c语言—贪吃蛇

  • 基于Ncurse图形库的C语言小游戏
    • 项目运行环境:Linux,
    • 项目的目的和意义:起到承上启下的作用,对于前面学的C语言的基础和指针、结构体、链表等做一个比较好的巩固,对于后面的Linux系统编程的开发做铺垫
    • 项目基础要求:C语言基础、Linux基本操作
1.为什么要使用ncurse库

因为使用c语言中自带的 getchar、scanf函数获取一个字符需要键盘输入结果之后,按一下回车才可以。影响效果。

使用 ncurses 库的主要原因是,它允许在字符模式下进行复杂的终端界面开发,并且可以直接读取按键输入,无需按下回车键。这样可以实现更实时的交互效果,提升用户体验。

2.ncurse库
#include <curses.h>

int main() {

initscr(); // 初始化 ncurses 库和屏幕

printw("this is a curses window.\n");

getch(); // 等待用户输入一个字符

endwin(); // 结束 ncurses 模式,恢复正常的终端模式

return 0;

}
详细解释 initscr():
  1. initscr();:
    • initscr() 是 ncurses 库的初始化函数。调用这个函数后,终端将进入 ncurses 模式,程序可以使用 ncurses 的各种功能来控制终端显示和处理输入。
    • 初始化 ncurses 库。这是使用 ncurses 的第一步,必须调用这个函数来设置库内部的数据结构,并准备终端窗口进行操作。
  1. printw("this is a curses window.\n");:
    • 在当前窗口中打印字符串 "this is a curses window.\n"。printw 函数类似于标准的 printf,但用于 ncurses 窗口。
  1. getch();:
    • 等待用户输入一个字符。程序在这里暂停,直到用户按下一个键。这样可以让用户有时间看到打印的字符串。
  1. endwin();:
    • 结束 ncurses 模式,恢复终端的正常操作状态。调用这个函数后,终端会返回到普通模式,之前由 ncurses 修改的终端设置都会恢复。
  1. return 0;:
    • 返回值 0 表示程序成功执行完毕。

这段代码的作用是创建一个 ncurses 窗口,显示一行文本,并等待用户输入任意字符后退出。它演示了 ncurses 库的基本使用方法。

1.2.2 编译ncurse的程序:

编译 :gcc 文件名.c -lcurses 或:gcc 文件名.c -lncurses

1.2.3 输入一个按键 ncurse库的响应速度:
#include <curses.h>  // 包含curses库的头文件

int main()
{
	initscr();  // 初始化curses模式
	printw("this is a curss window .\n");  // 在窗口中打印字符串
	getch();  // 等待用户按下一个键
	endwin();  // 结束curses模式,恢复终端到正常模式
	
	return 0;  // 返回0,表示程序正常结束
}

  • 使用ncurse的好处是:按下一个按键不需要按下回车,直接就可以输出对应的值,可以实现更实时的交互效果,提升用户体验。
1.3 ncurse上下左右键获取:
1.3.1 如何查看宏定义的.h文件:

vi /usr/include/curses.h //查看宏定义.h文件的指令
:q //退出查看

1.3.2 ncurse上下左右键获取:

#include <curses.h>  // 包含curses库的头文件

int main()
{
	char c;  // 字符变量,用于接收输入
	int key;  // 整型变量,用于接收键盘输入

	initscr();       // 初始化curses模式
	keypad(stdscr, 1);  // 启用标准屏幕的键盘输入

	while(1) {  // 进入无限循环,等待用户输入
		key = getch();  // 获取用户输入的键值

		switch(key) {  // 根据键值进行不同的操作
			case KEY_DOWN:  // 如果是向下箭头键
				printw("DOWN\n");  // 在屏幕上打印DOWN
				break;
			case KEY_UP:  // 如果是向上箭头键
				printw("UP\n");  // 在屏幕上打印UP
				break;
			case KEY_LEFT:  // 如果是向左箭头键
				printw("LEFT\n");  // 在屏幕上打印LEFT
				break;
			case KEY_RIGHT:  // 如果是向右箭头键
				printw("RIGHT\n");  // 在屏幕上打印RIGHT
				break;
		}
	}

	endwin();  // 结束curses模式,恢复终端到正常模式
	
	return 0;  // 返回0,表示程序正常结束
}
  • 我们按上下左右键位之后,可以获取到上下左右的打印信息

二、地图规划

2.1 地图规划算法显示第一行:
#include <curses.h>  // 包含curses库的头文件

// 初始化curses模式的函数
void initNcurse()
{
	initscr();       // 初始化curses模式
	keypad(stdscr, 1);  // 启用标准屏幕的键盘输入
}

// 绘制游戏界面的函数
void gamePic()
{
	int hang;  // 行变量
	int lie;   // 列变量

	for(hang = 0; hang < 20; hang++) {
		if(hang == 0) {  // 如果是第一行
			for(lie = 0; lie < 20; lie++) {
				printw("--");  // 打印顶部边界
			}
			printw("\n");
			for(lie = 0; lie <= 20; lie++) {
				if(lie == 0 || lie == 20) {
					printw("|");  // 打印左右边界
				} else {
					printw("  ");  // 打印空白区域
				}
			}
			printw("\n");
		}
		
	}
}

int main()
{
	
	initNcurse();  // 初始化curses模式
	gamePic();     // 绘制游戏界面

	getch();       // 等待用户按下一个键
	endwin();      // 结束curses模式,恢复终端到正常模式
	
	return 0;      // 返回0,表示程序正常结束
}

有些编译器,处理能力不强 ,

for(lie = 0 ;lie<20;lie++){

if(lie == 0 || lie == 19){

printw("|");

}

按照正常思路,并不能达到预期效果,需要使用两个if判断语句

for(lie = 0 ;lie<20;lie++){

if(lie == 0 ) {

printw("|");

}

if(lie == 19){

printw("|");

}

2.2 实现贪吃蛇完整地图:
#include <curses.h>  // 包含curses库的头文件
// 初始化curses模式的函数
void initNcurse()
{
	initscr();  // 初始化curses模式
	keypad(stdscr, 1);  // 启用标准屏幕的键盘输入
}

// 绘制游戏界面的函数
void gamePic()
{
	int hang;  // 行变量
	int lie;   // 列变量

	for(hang = 0; hang < 20; hang++) {
		if(hang == 0) {  // 如果是第一行
			for(lie = 0; lie < 20; lie++) {
				printw("--");  // 打印顶部边界
			}
			printw("\n");
			for(lie = 0; lie <= 20; lie++) {
				if(lie == 0 || lie == 20) {
					printw("|");  // 打印左右边界
				} else {
					printw("  ");  // 打印空白区域
				}
			}
			printw("\n");
		}
		if(hang > 0 && hang <= 18) {  // 如果是中间行
			for(lie = 0; lie <= 20; lie++) {
				if(lie == 0 || lie == 20) {
					printw("|");  // 打印左右边界
				} else {
					printw("  ");  // 打印空白区域
				}
			}
			printw("%d", hang);  // 打印行号
			printw("\n");
		}
		if(hang == 19) {  // 如果是最后一行
			for(lie = 0; lie <= 20; lie++) {
				if(lie == 0 || lie == 20) {
					printw("|");  // 打印左右边界
				} else {
					printw("  ");  // 打印空白区域
				}
			}
			printw("\n");
			for(lie = 0; lie < 20; lie++) {
				printw("--");  // 打印底部边界
			}
			printw("\n");
			printw("by LXL\n");  // 打印作者名
		}
	}
}

int main()
{
	initNcurse();  // 初始化curses模式
	gamePic();  // 绘制游戏界面

	getch();  // 等待用户按下一个键
	endwin();  // 结束curses模式,恢复终端到正常模式
	
	return 0;  // 返回0,表示程序正常结束
}

2.3 优化贪吃蛇地图:
#include <curses.h>  // 包含curses库的头文件

// 初始化curses模式的函数
void initNcurse()
{
	initscr();       // 初始化curses模式
	keypad(stdscr, 1);  // 启用标准屏幕的键盘输入
}

// 绘制游戏界面的函数
void gamePic()
{
	int hang;  // 行变量
	int lie;   // 列变量

	for(hang = 0; hang <= 20; hang++) {  // 循环绘制行,包括顶部和底部边界
		if(hang == 0 || hang == 20) {  // 如果是第一行或最后一行
			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 {
					printw("  ");  // 打印空白区域
				}
			}
			// printw("%d\n", hang);  // 打印行号
		}
	}

	printw("By LXL2\n");  // 打印作者名
}

int main()
{
	initNcurse();  // 初始化curses模式
	gamePic();     // 绘制游戏界面

	getch();       // 等待用户按下一个键
	endwin();      // 结束curses模式,恢复终端到正常模式
	
	return 0;      // 返回0,表示程序正常结束
}

三、显示贪吃蛇身子

3.1 显示贪吃蛇身子的一个节点:
#include <curses.h>

// 定义贪吃蛇的结构体
struct Snake
{
    int hang; // 行
    int lie;  // 列
    struct Snake *next; // 指向下一个节点的指针
};

// 初始化一个节点
struct Snake node1 = {2, 2, NULL};

// 初始化NCurses库
void initNcurse()
{
    initscr();          // 初始化屏幕
    keypad(stdscr, 1);  // 启用键盘输入
}

// 绘制游戏画面
void gamPic()
{
    int hang;
    int lie;

    // 循环绘制行
    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 (node1.hang == hang && node1.lie == lie) {
                    // 绘制贪吃蛇的身体
                    printw("[]");
                } else {
                    // 绘制空白
                    printw("  ");
                }
            }
            printw("\n");
        }

        // 绘制下边框和作者信息
        if (hang == 19) {
            for (lie = 0; lie < 20; lie++) {
                printw("--");
            }
            printw("\n");
            printw("By lxl3!\n");
        }
    }
}

// 主函数
int main()
{
    initNcurse();  // 初始化NCurses
    gamPic();      // 绘制游戏画面
    
    getch();       // 等待用户输入
    endwin();      // 结束NCurses模式
    return 0;      // 返回0,表示程序正常结束
}

3.2 使用链表显示贪吃蛇身子:
#include <curses.h>

struct Snake
{
	int hang;   // 行
	int lie;    // 列
	struct Snake *next;  // 下一个节点指针
};

// 定义蛇的节点
struct Snake node1 = {2, 2, NULL};
struct Snake node2 = {2, 3, NULL};
struct Snake node3 = {2, 4, NULL};
struct Snake node4 = {2, 5, NULL};

// 初始化 ncurses 函数
void initNcurse()
{
	initscr();  // 初始化 ncurses
	keypad(stdscr, 1);  // 启用键盘输入
}

// 判断指定位置是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p;
	p = &node1;  // 从第一个蛇节点开始遍历
	
	while (p != NULL) {
		if (p->hang == i && p->lie == j) {
			return 1;  // 如果找到蛇节点,返回1
		}
		p = p->next;  // 移动到下一个节点
	}
	return 0;  // 没有找到蛇节点,返回0
}

// 绘制游戏界面函数
void gamePic()
{
	int hang;
	int lie;
	
	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 (hasSnakeNode(hang, lie)) {
					printw("[]");  // 打印蛇的节点
				} else {
					printw("  ");  // 打印空白区域
				}
			}
			printw("\n");
		}
		
		if (hang == 19) {
			for (lie = 0; lie < 20; lie++) {
				printw("--");  // 打印底部边界
			}
			printw("\n");
			printw("by LXL5\n");  // 打印作者信息
		}
	}
}

// 主函数
int main()
{
	initNcurse();  // 初始化 ncurses
	
	// 设置蛇节点之间的关系
	node1.next = &node2;
	node2.next = &node3;
	node3.next = &node4;
	
	gamePic();  // 绘制游戏界面
	
	getch();  // 等待用户输入
	endwin();  // 关闭 ncurses
	
	return 0;
}

3.3使用链表显示贪吃蛇完整的身子:
#include <curses.h>
 
struct Snake
{
	int hang;  // 行
	int lie;   // 列
	struct Snake *next;  // 下一个节点指针
};
 
// 定义蛇的节点
struct Snake node1 = {2, 2, NULL};
struct Snake node2 = {2, 3, NULL};
struct Snake node3 = {2, 4, NULL};
struct Snake node4 = {2, 5, NULL};
 
// 初始化 ncurses
void initNcurse()
{
	initscr();  // 初始化 ncurses
	keypad(stdscr, 1);  // 启用键盘输入
}
 
// 判断指定位置是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = &node1;  // 从第一个蛇节点开始检查
 
	while (p != NULL){
		if (p->hang == i && p->lie == j){
			return 1;  // 如果找到蛇节点,返回1
		}
		p = p->next;  // 移动到下一个节点
	}
	return 0;  // 没有找到蛇节点,返回0
}
 
// 绘制游戏界面
void gamPic()
{
	int hang;
	int lie;
	
	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 (hasSnakeNode(hang, lie)){
					printw("[]");  // 打印蛇的节点
				} else {
					printw("  ");  // 打印空白区域
				}
			}
			printw("\n");
		}
 
		if (hang == 19){
			for (lie = 0; lie < 20; lie++){
				printw("--");  // 打印底部边界
			}
			printw("\n");
			printw("By lxl!\n");  // 打印作者信息
		}
	}
}
 
// 主函数
int main()
{
	initNcurse();  // 初始化 ncurses
 
	// 设置蛇的节点之间的关系
	node1.next = &node2;
	node2.next = &node3;
	node3.next = &node4;
 
	gamPic();  // 绘制游戏界面
	
	getch();  // 等待用户输入
	endwin();  // 关闭 ncurses
	return 0;
}

3.4 显示贪吃蛇完整身子改进:
#include <curses.h>
#include <stdlib.h>

struct Snake
{
	int hang;  // 行
	int lie;   // 列
	struct Snake *next;  // 下一个节点指针
};

struct Snake *head = NULL;  // 蛇头指针
struct Snake *tail = NULL;  // 蛇尾指针

// 初始化 ncurses
void initNcurse()
{
	initscr();  // 初始化 ncurses
	keypad(stdscr, 1);  // 启用键盘输入
}

// 判断指定位置是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;  // 从蛇头开始检查

	while (p != NULL){
		if (p->hang == i && p->lie == j){
			return 1;  // 如果找到蛇节点,返回1
		}
		p = p->next;  // 移动到下一个节点
	}
	return 0;  // 没有找到蛇节点,返回0
}

// 绘制游戏界面
void gamePic()
{
	int hang;
	int lie;
	
	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 (hasSnakeNode(hang, lie)){
					printw("[]");  // 打印蛇的节点
				} else {
					printw("  ");  // 打印空白区域
				}
			}
			printw("\n");
		}
 
		if (hang == 19){
			for (lie = 0; lie < 20; lie++){
				printw("--");  // 打印底部边界
			}
			printw("\n");
			printw("by LXL6\n");  // 打印作者信息
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake));  // 分配新节点内存
	
	fresh->hang = tail->hang;
	fresh->lie = tail->lie + 1;  // 在蛇尾的右侧添加新节点
	fresh->next = NULL;
	
	tail->next = fresh;  // 将新节点链接到当前尾部节点的后面
	tail = fresh;  // 更新尾部指针为新节点
}

// 初始化蛇
void initSnake()
{
	head = (struct Snake *)malloc(sizeof(struct Snake));  // 创建蛇头节点
	head->hang = 2;
	head->lie = 2;
	head->next = NULL;

	tail = head;  // 初始时头尾相同
	
	addNode();  // 添加初始长度的节点
	addNode();
	addNode();
}

// 主函数
int main()
{
	initNcurse();  // 初始化 ncurses
	
	initSnake();  // 初始化蛇的身体
	
	gamePic();  // 绘制游戏界面
	
	getch();  // 等待用户输入
	endwin();  // 关闭 ncurses
	
	return 0;
}

注意:因为编译器版本的不同,如果使用new为函数名,可能会报错,因为在c++中,new属于关键字。换个关键字即可。

四、贪吃蛇移动

4.1 按下▶贪吃蛇向右移动:
#include <curses.h>
#include <stdlib.h>   

// 定义蛇的结构体
struct Snake
{
	int hang; // 蛇节点的行
	int lie;  // 蛇节点的列
	struct Snake *next; // 指向下一个节点的指针
};

struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}

// 绘制游戏界面
void gamePic()
{
	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 (hasSnakeNode(hang, lie)) {
					printw("[]"); // 绘制蛇节点
				} else {
					printw("  "); // 绘制空白区域
				}
			}
			printw("\n");
		}
		if (hang == 19) { // 绘制底部边框
			for (lie = 0; lie < 20; lie++) {
				printw("--");
			}
			printw("\n");
			printw("by LXL6\n"); // 显示作者信息
		}
	}
	refresh(); // 刷新屏幕
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
	fresh->hang = tail->hang; // 新节点的行与尾节点相同
	fresh->lie = tail->lie - 1; // 新节点的列在尾节点的左边
	fresh->next = NULL; // 新节点的下一个节点为空
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 删除蛇节点
void deleNode()
{
	struct Snake *p = head; // 指向头节点
	head = head->next; // 头节点指向下一个节点
	
	free(p); // 释放内存
}

// 移动蛇
void moveSnake()
{
	addNode(); // 添加新节点
	deleNode(); // 删除旧节点
}

// 初始化蛇
void initSnake()
{
	head = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给头节点
	head->hang = 2; // 设置头节点的行
	head->lie = 18; // 设置头节点的列
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	// 添加初始长度的节点
	addNode();
	addNode();
	addNode();
	addNode();
}

// 主函数
int main()
{
	int con;
	initNcurse(); // 初始化 ncurses
	initSnake(); // 初始化蛇
	
	gamePic(); // 绘制游戏界面

	while (1) {
		con = getch(); // 获取用户输入的键值
		if (con == KEY_LEFT) { // 如果用户按下左箭头键
			moveSnake(); // 移动蛇
			gamePic(); // 更新游戏界面
		}
	}
	
	getch(); // 等待用户输入
	endwin(); // 结束 ncurses 模式
	
	return 0;
}

  • move(0,0); 是一个 ncurses 库中的函数调用。它的作用是将终端上的光标移动到指定的位置,其中 (0, 0) 表示终端的左上角位置。
  • 在终端屏幕上,坐标通常以左上角为起点,向右为正 lie(列),向下为正 hang(行)。因此,move(0, 0); 就是将光标移动到终端的最左上角的位置,使得接下来的输出从这个位置开始。
  • 在游戏编程中,这样的调用通常用于确保屏幕上的输出位置或者清空屏幕时使用。
4.2 贪吃蛇撞墙重新开始:
#include <curses.h>
#include <stdlib.h>   

// 定义蛇的结构体
struct Snake
{
	int hang; // 蛇节点的行
	int lie;  // 蛇节点的列
	struct Snake *next; // 指向下一个节点的指针
};

struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}

// 绘制游戏界面
void gamePic()
{
	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 (hasSnakeNode(hang, lie)) {
					printw("[]"); // 绘制蛇节点
				} else {
					printw("  "); // 绘制空白区域
				}
			}
			printw("\n");
		}
		if (hang == 19) { // 绘制底部边框
			for (lie = 0; lie < 20; lie++) {
				printw("--"); // 打印水平边框
			}
			printw("\n");
			printw("by LXL8\n"); // 显示作者信息
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
	fresh->hang = tail->hang; // 新节点的行与尾节点相同
	fresh->lie = tail->lie + 1; // 新节点的列在尾节点的右边
	fresh->next = NULL; // 新节点的下一个节点为空
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 初始化蛇
void initSnake()
{	
	struct Snake *p;
	while(head != NULL){
		 p = head;
		head = head->next;
		free(p); // 释放节点内存
	}
	head = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给头节点
	
	head->hang = 2; // 设置头节点的行
	head->lie = 1; // 设置头节点的列
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
}

// 删除蛇节点
void deleNode()
{
	struct Snake *p;
	p = head;
	head = head->next; // 头节点移动到下一个节点
	
	free(p); // 释放节点内存
}

// 移动蛇
void moveSnake()
{
	addNode(); // 添加新的蛇节点
	deleNode(); // 删除最早的蛇节点
	
	// 如果蛇头碰到边界,重新初始化蛇
	if (tail->hang == 0 || tail->lie == 0 || tail->hang == 20 || tail->lie == 20) {
		initSnake();
	}
}

int main()
{
	int con;
	
	initNcurse(); // 初始化 ncurses
	initSnake(); // 初始化蛇
	gamePic(); // 绘制游戏界面

	while (1) {
		con = getch();
		if (con == KEY_RIGHT) { // 按下右箭头键时移动蛇
			moveSnake();
			gamePic();
		}
	}
	
	getch(); // 等待用户输入
	endwin(); // 结束 ncurses 模式
	
	return 0;
}
4.3 贪吃蛇脱缰自由向右行走
#include <curses.h>
#include <stdlib.h>   
#include <unistd.h>

// 定义蛇的结构体
struct Snake
{
	int hang; // 蛇节点的行
	int lie;  // 蛇节点的列
	struct Snake *next; // 指向下一个节点的指针
};

struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}

// 绘制游戏界面
void gamePic()
{
	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 (hasSnakeNode(hang, lie)) {
					printw("[]"); // 绘制蛇节点
				} else {
					printw("  "); // 绘制空白区域
				}
			}
			printw("\n");
		}
		if (hang == 19) { // 绘制底部边框
			for (lie = 0; lie < 20; lie++) {
				printw("--");
			}
			printw("\n");
			printw("by LXL9\n"); // 显示作者信息
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
	fresh->hang = tail->hang; // 新节点的行与尾节点相同
	fresh->lie = tail->lie + 1; // 新节点的列在尾节点的右边
	fresh->next = NULL; // 新节点的下一个节点为空
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 初始化蛇
void initSnake()
{	
	struct Snake *p;
	while(head != NULL){
		 p = head;
		head = head->next;
		free(p);
		
	}
	head = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给头节点
	head->hang = 2; // 设置头节点的行
	head->lie = 1; // 设置头节点的列
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
}
void deleNode()
{
	struct Snake *p;
	p = head;
	head = head->next;
	
	free(p);
}
void moveSnake()
{
	
	
	addNode();
	deleNode();
	
	if(tail->hang == 0 || tail->lie == 0 || tail->hang == 20 || tail->lie == 20 ){
		
		initSnake();
	}
	
	
}
int main()
{
	int con;
	
	initNcurse(); // 初始化 ncurses
	 // 初始化蛇
	initSnake();
	gamePic(); // 绘制游戏界面

	while(1){
			
			
				moveSnake();
				gamePic();
				
                refresh(); //刷新
				usleep(100000); //微秒
				
		}
	
	getch(); // 等待用户输入
	endwin(); // 结束 ncurses 模式
	
	return 0;
}
  1. refresh() 函数
    • 在使用 ncurses 库时,屏幕的输出并不会实时更新,而是在程序中调用 refresh() 函数时才会将缓冲区中的内容刷新到屏幕上。
    • 在你的游戏中,gamePic() 函数用来绘制游戏界面,但绘制完后需要调用 refresh() 函数来将界面显示出来,否则用户看不到更新的内容。
  1. usleep(100000) 函数
    • usleep(100000) 是一个睡眠函数,它使程序暂停执行一段时间,单位是微秒(百万分之一秒)。
    • 在你的程序中,usleep(100000) 可能用于控制游戏界面的刷新频率,即每次移动蛇后,程序暂停 100 毫秒(0.1 秒),以控制游戏速度或者动画效果的流畅性。

综上所述,这两个函数在你的程序中配合使用,refresh() 用于更新屏幕显示,而 usleep(100000) 用于控制游戏界面的刷新速度,使得游戏看起来更加平滑和可控。

五、Linux线程引入

5.1 贪吃蛇方向移动和刷新界面一起实现面临的问题:
#include <curses.h>
#include <stdlib.h>   
#include <unistd.h>

// 定义蛇的结构体
struct Snake
{
	int hang; // 蛇节点的行
	int lie;  // 蛇节点的列
	struct Snake *next; // 指向下一个节点的指针
};

struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}

// 绘制游戏界面
void gamePic()
{
	int hang;
	int lie;
	
	move(0,0); // 将光标移动到 (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 (hasSnakeNode(hang, lie)) {
					printw("[]"); // 绘制蛇节点
				} else {
					printw("  "); // 绘制空白区域
				}
			}
			printw("\n");
		}
		if (hang == 19) { // 绘制底部边框
			for (lie = 0; lie < 20; lie++) {
				printw("--"); // 打印底部边框线
			}
			printw("\n");
			printw("by LXL9\n"); // 显示作者信息
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
	fresh->hang = tail->hang; // 新节点的行与尾节点相同
	fresh->lie = tail->lie + 1; // 新节点的列在尾节点的右边
	fresh->next = NULL; // 新节点的下一个节点为空
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 初始化蛇
void initSnake()
{	
	struct Snake *p;
	while(head != NULL){
		 p = head;
		head = head->next;
		free(p); // 释放节点内存
	}
	head = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给头节点
	head->hang = 2; // 设置头节点的行
	head->lie = 1; // 设置头节点的列
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
}

// 删除蛇节点
void deleNode()
{
	struct Snake *p;
	p = head;
	head = head->next; // 头节点指向下一个节点
	free(p); // 释放节点内存
}

// 移动蛇
void moveSnake()
{
	addNode(); // 添加新节点
	deleNode(); // 删除尾节点

	// 如果蛇头移动到边界,重新初始化蛇
	if (tail->hang == 0 || tail->lie == 0 || tail->hang == 20 || tail->lie == 20 ){
		initSnake();
	}
}

int main()
{
	int key;
	
	initNcurse(); // 初始化 ncurses 库
	initSnake(); // 初始化蛇
	gamePic(); // 绘制游戏界面

	while(1){
		moveSnake(); // 移动蛇
		gamePic(); // 绘制更新后的游戏界面
		refresh(); // 刷新屏幕显示
		usleep(100000); // 暂停一段时间,控制游戏速度
	}

	getch(); // 等待用户输入
	endwin(); // 结束 ncurses 模式
	
	return 0;
}
  • 在上面的程序中main函数中有两个while(1)循环,这样就会出现问题,程序运行的现象是:获取按键值的这个while循环根本不会执行,因为一直在第一个while循环中,属于si循环,永远无法进入第二个循环当中,那么我们该如何解决?
  • “Linux线程”!
  • 在贪吃蛇运行过程中,我们需要改变贪吃蛇的移动方向,就是需要不停的去扫描键盘输入的值来判断蛇下一步的方向,同时还需要不停的刷新界面,为了同时使用多个while循环并存,这里需要使用linux线程。
5.2 线程的基本用法:
#include <stdio.h>
#include <pthread.h>

void* fun1()
{
	while(1){
			printf("this is func 1\n");
			sleep(1);
		}
	
}
void* fun2()
{
	while(1){
			printf("this is func 2\n");
			sleep(1);
		}
	
}
void* fun3()
{
	while(1){
			printf("this is func 3\n");
			sleep(1);
		}
	
}

int main()
{
	pthread_t th1;
	pthread_t th2;
	pthread_t th3;
	
	pthread_create(&th1,NULL,fun1,NULL);
	pthread_create(&th2,NULL,fun2,NULL);
	pthread_create(&th3,NULL,fun3,NULL);
	
	while(1);
	
	return 0;
}

1. 包含头文件
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> // 为了使用sleep函数
  • #include <stdio.h>:用于标准输入输出功能。
  • #include <pthread.h>:用于pthread库的多线程功能。
  • #include <unistd.h>:用于sleep函数。
2. 定义线程函数
void* fun1() {
    while (1) {
        printf("this is func 1\n");
        sleep(1);
    }
}

void* fun2() {
    while (1) {
        printf("this is func 2\n");
        sleep(1);
    }
}

void* fun3() {
    while (1) {
        printf("this is func 3\n");
        sleep(1);
    }
}
  • fun1、fun2、fun3:三个线程函数,每个函数在一个无限循环中,每秒钟打印一次特定的消息。
  • while (1):无限循环,使线程函数一直运行。
  • printf:打印特定消息到标准输出。
  • sleep(1):使线程休眠1秒,以便在打印消息之间有间隔。
3. 主函数
int main() {
    pthread_t th1;
    pthread_t th2;
    pthread_t th3;

    pthread_create(&th1, NULL, fun1, NULL);
    pthread_create(&th2, NULL, fun2, NULL);
    pthread_create(&th3, NULL, fun3, NULL);

    while (1);

    return 0;
}
  • pthread_t th1, th2, th3:定义三个线程变量,用于保存线程ID。
  • pthread_create(&th1, NULL, fun1, NULL):
    • 参数1:&th1,传出参数,用于保存系统分配的线程ID。
    • 参数2:NULL,使用默认线程属性。
    • 参数3:fun1,线程函数的函数指针。
    • 参数4:NULL,线程函数的参数,此处没有参数传递。
  • pthread_create(&th2, NULL, fun2, NULL):类似地,创建第二个线程。
  • pthread_create(&th3, NULL, fun3, NULL):类似地,创建第三个线程。
  • while (1):主线程进入无限循环,保持程序运行。否则,主线程结束后,所有子线程也会终止。
总结
  • 该程序创建了三个线程,每个线程分别运行fun1、fun2、fun3函数,并在每秒钟打印一次消息。
  • 主线程进入无限循环,防止程序结束,从而使子线程持续运行。
  • 每个线程都在独立地打印消息,展示了基本的多线程编程模型。
5.3 使用线程解决贪吃蛇方向移动和刷新界面一起实现面临的问题:
#include <curses.h>
#include <stdlib.h>   
#include <unistd.h>
#include <pthread.h> // 包含多线程相关的头文件

// 定义蛇的结构体
struct Snake
{
	int hang; // 蛇节点的行
	int lie;  // 蛇节点的列
	struct Snake *next; // 指向下一个节点的指针
};

struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针
int key; // 全局变量,存储键盘输入的方向键值

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}

// 绘制游戏界面
void gamePic()
{
	int hang;
	int lie;
	
	move(0,0); // 将光标移动到 (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 (hasSnakeNode(hang, lie)) {
					printw("[]"); // 绘制蛇节点
				} else {
					printw("  "); // 绘制空白区域
				}
			}
			printw("\n");
		}
		if (hang == 19) { // 绘制底部边框
			for (lie = 0; lie < 20; lie++) {
				printw("--"); // 打印底部边框线
			}
			printw("\n");
			printw("by LXL11,key = %d\n", key); // 显示作者信息和当前键值
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
	fresh->hang = tail->hang; // 新节点的行与尾节点相同
	fresh->lie = tail->lie + 1; // 新节点的列在尾节点的右边
	fresh->next = NULL; // 新节点的下一个节点为空
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 初始化蛇
void initSnake()
{	
	struct Snake *p;
	while(head != NULL){
		 p = head;
		head = head->next;
		free(p); // 释放节点内存
	}
	head = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给头节点
	head->hang = 2; // 设置头节点的行
	head->lie = 1; // 设置头节点的列
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
}

// 删除蛇节点
void* deleNode()
{
	struct Snake *p;
	p = head;
	head = head->next; // 头节点指向下一个节点
	free(p); // 释放节点内存
}

// 移动蛇
void* moveSnake()
{
	addNode(); // 添加新节点
	deleNode(); // 删除尾节点

	// 如果蛇头移动到边界,重新初始化蛇
	if (tail->hang == 0 || tail->lie == 0 || tail->hang == 20 || tail->lie == 20 ){
		initSnake();
	}
}

// 刷新界面线程函数
void* refreshJieMian(void *arg)
{
	while(1){
		moveSnake(); // 移动蛇
		gamePic(); // 绘制更新后的游戏界面
		refresh(); // 刷新屏幕显示
		usleep(100000); // 暂停一段时间,控制游戏速度
	}
}

// 监听键盘输入线程函数
void* changDir(void *arg)
{
	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;
		}
	}
}

int main()
{
	initNcurse(); // 初始化 ncurses
	initSnake(); // 初始化蛇
	gamePic(); // 绘制游戏界面

	pthread_t t1, t2; // 定义两个线程变量

	pthread_create(&t1, NULL, refreshJieMian, NULL); // 创建刷新界面线程
	pthread_create(&t2, NULL, changDir, NULL); // 创建监听键盘输入线程

	while(1); // 主线程循环等待

	getch(); // 等待用户输入
	endwin(); // 结束 ncurses 模式
	
	return 0;
}

  • 蛇在向右移动的同时,按方向键引导蛇移动方向,这就是引入线程之后的效果!

六、贪吃蛇跑起来

6.1 实现贪吃蛇四方向的风骚走位:
#include <curses.h>
#include <stdlib.h>   
#include <unistd.h>
#include <pthread.h>

// 定义全局变量
#define UP    1
#define DOWN  2
#define LEFT  3
#define RIGHT 4

// 定义蛇的结构体
struct Snake
{
	int hang; // 蛇节点的行
	int lie;  // 蛇节点的列
	struct Snake *next; // 指向下一个节点的指针
};

struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针
int key;  // 获取方向
int dir;   // 方向

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}

// 绘制游戏界面
void gamePic()
{
	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 (hasSnakeNode(hang, lie)) {
					printw("[]"); // 绘制蛇节点
				} else {
					printw("  "); // 绘制空白区域
				}
			}
			printw("\n");
		}
		if (hang == 19) { // 绘制底部边框
			for (lie = 0; lie < 20; lie++) {
				printw("--");
			}
			printw("\n");
			printw("by LXL11,key = %d\n",key); // 显示作者信息
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
	fresh->next = NULL; // 新节点的下一个节点为空
	
	switch(dir){
		   case UP:
			   fresh->hang = tail->hang - 1; // 新节点的行与尾节点相同
			   fresh->lie = tail->lie; // 新节点的列在尾节点的右边
			   break;
			case DOWN:
			   fresh->hang = tail->hang +1; // 新节点的行与尾节点相同
			   fresh->lie = tail->lie; // 新节点的列在尾节点的右边
			   break;
			case LEFT:
			   fresh->hang = tail->hang; // 新节点的行与尾节点相同
			   fresh->lie = tail->lie-1; // 新节点的列在尾节点的右边
			   break;
			case RIGHT:
			   fresh->hang = tail->hang; // 新节点的行与尾节点相同
			   fresh->lie = tail->lie+1; // 新节点的列在尾节点的右边
			   break;
		}
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 初始化蛇
void initSnake()
{	
	struct Snake *p;
	
	dir = RIGHT;  //初始化蛇向右移动
	
	while(head != NULL){
		p = head;
		head = head->next;
		free(p);
		
	}
	head = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给头节点
	head->hang = 2; // 设置头节点的行
	head->lie = 1; // 设置头节点的列
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
}

// 删除蛇节点
void deleNode()
{
	struct Snake *p;
	p = head;
	head = head->next;
	
	free(p);
}

// 移动蛇
void moveSnake()
{
	addNode();
	deleNode();
	
	if(tail->hang == 0 || tail->lie == 0 || tail->hang == 20 || tail->lie == 20 ){
		
		initSnake();
	}
}

// 刷新界面线程函数
void* refreshJieMian(void *arg)
{
	while(1){
		moveSnake();
		gamePic();
		refresh();
		usleep(100000);
	}
}

// 改变方向线程函数
void* changDir(void *arg)
{
	while(1){
		key = getch();
		switch(key){
			case KEY_DOWN:
				break;	
			case KEY_UP:
				dir = UP;
				break;
			case KEY_LEFT:
				dir = LEFT;
				break;
			case KEY_RIGHT:
				dir = RIGHT;
				break;
		}
	}	
}

// 主函数
int main()
{
	initNcurse(); // 初始化 ncurses
	initSnake(); // 初始化蛇
	gamePic(); // 绘制游戏界面
	
	pthread_t t1;
	pthread_t t2;
	
	pthread_create(&t1,NULL,refreshJieMian,NULL); // 创建刷新界面线程
	pthread_create(&t2,NULL,changDir,NULL); // 创建改变方向线程
	
	while(1); // 主线程循环等待
	
	getch(); // 等待用户输入
	endwin(); // 结束 ncurses 模式
	
	return 0;
}
6.2 用绝对值方式来解决不合理的走位:
#include <curses.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 hang; // 蛇节点的行
	int lie;  // 蛇节点的列
	struct Snake *next; // 指向下一个节点的指针
};

struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针
int key;  // 用户输入的键值
int dir;  // 当前蛇的移动方向

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
	noecho(); // 禁止输入字符时的回显
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}

// 绘制游戏界面
void gamePic()
{
	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 (hasSnakeNode(hang, lie)) {
					printw("[]"); // 绘制蛇节点
				} else {
					printw("  "); // 绘制空白区域
				}
			}
			printw("\n");
		}
		if (hang == 19) { // 绘制底部边框
			for (lie = 0; lie < 20; lie++) {
				printw("--");
			}
			printw("\n");
			printw("by LXL11,key = %d\n",key); // 显示作者信息
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
	fresh->next = NULL; // 新节点的下一个节点为空
	
	switch(dir){
		case UP:
			fresh->hang = tail->hang - 1; // 新节点的行与尾节点相同
			fresh->lie = tail->lie; // 新节点的列在尾节点的右边
			break;
		case DOWN:
			fresh->hang = tail->hang + 1; // 新节点的行与尾节点相同
			fresh->lie = tail->lie; // 新节点的列在尾节点的右边
			break;
		case LEFT:
			fresh->hang = tail->hang; // 新节点的行与尾节点相同
			fresh->lie = tail->lie - 1; // 新节点的列在尾节点的右边
			break;
		case RIGHT:
			fresh->hang = tail->hang; // 新节点的行与尾节点相同
			fresh->lie = tail->lie + 1; // 新节点的列在尾节点的右边
			break;
	}
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 初始化蛇
void initSnake()
{	
	struct Snake *p;
	
	dir = RIGHT; // 初始化蛇向右移动
	
	while(head != NULL){
		p = head;
		head = head->next;
		free(p);
	}
	
	head = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给头节点
	head->hang = 2; // 设置头节点的行
	head->lie = 1; // 设置头节点的列
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	// 添加初始节点
	addNode();
	addNode();
	addNode();
	addNode();
}

// 删除蛇尾节点
void* deleNode()
{
	struct Snake *p;
	p = head;
	head = head->next;
	free(p);
}

// 移动蛇
void* moveSnake()
{
	addNode(); // 添加新节点
	deleNode(); // 删除尾节点
	
	// 检查蛇是否撞墙,若撞墙则重新初始化蛇
	if(tail->hang == 0 || tail->lie == 0 || tail->hang == 20 || tail->lie == 20 ){
		initSnake();
	}
}

// 刷新界面线程函数
void* refreshJieMian(void *arg)
{
	while(1){
		moveSnake(); // 移动蛇
		gamePic(); // 绘制游戏界面
		refresh(); // 刷新屏幕
		
		usleep(100000); // 等待一段时间
	}
}

// 改变方向的函数
void turn(int direction)
{
	// 如果当前方向与要改变的方向不在同一轴上(例如当前向上移动,不能立即向下移动),
	if(abs(dir) != abs(direction)){
		dir = direction;  //则改变方向
	}
}

// 改变方向线程函数
void* changDir(void *arg)
{
	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()
{
	initNcurse(); // 初始化 ncurses
	initSnake(); // 初始化蛇
	gamePic(); // 绘制游戏界面
	
	pthread_t t1; // 刷新界面线程
	pthread_t t2; // 改变方向线程
	
	// 创建线程
	pthread_create(&t1, NULL, refreshJieMian, NULL);
	pthread_create(&t2, NULL, changDir, NULL);
	
	while(1); // 主线程无限循环
	
	getch(); // 等待用户输入
	endwin(); // 结束 ncurses 模式
	
	return 0; // 返回主函数
}
1. 绝对值tmp
#include <stdio.h>

void main(){
	int a = 10;
	
	int b = -10;
	
	printf("abs a = %d\n,abs b = %d \n",abs(a),abs(b));
}

    • abs(a)abs(b) 分别计算了变量 ab 的绝对值。
    • printf 函数的格式化字符串被调整,确保输出结果的格式正确。
2. 贪吃蛇绝对值应用

void turn(int direction)

{

if(abs(dir) != abs(direction)){

dir = direction;

}

}

用来检查两个方向变量 dirdirection 的绝对值是否相同,如果不相同,则将 dir 设置为 direction 的值。

具体解释如下:

    • abs 函数abs() 是 C 标准库 <stdlib.h> 中的函数,用来返回一个整数的绝对值。
    • 逻辑判断:表达式 abs(dir) != abs(direction) 检查了 dirdirection 的绝对值是否相同。
      • 如果 dirdirection 的绝对值相同,表达式的值为假 (false),不执行后续操作。
      • 如果 dirdirection 的绝对值不同,表达式的值为真 (true),执行后续操作。
    • 赋值操作:如果表达式为真,则将 dir 的值设置为 direction 的值。
3. 用户输入字符时出现回显

  • noecho()是一个函数,它用于禁止用户输入字符时的回显效果。具体来说,它会使得用户在终端输入字符时,不会立即显示在屏幕上,而是直接传递给程序处理,通常用于密码输入等需要隐藏用户输入的场景。
  • 在ncurses初始化的时候 调用noecho函数

6.3 贪吃蛇吃饭了(食物的位置是固定的):
#include <curses.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 hang; // 蛇节点的行
	int lie;  // 蛇节点的列
	struct Snake *next; // 指向下一个节点的指针
};

struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针
int key; // 存储用户输入的键值
int dir; // 蛇的移动方向
struct Snake food; // 食物的位置信息

// 初始化食物的位置
void initFood()
{
	static int x = 2; // 静态变量,记录食物的行位置
	static int y = 2; // 静态变量,记录食物的列位置
	
	food.hang = x; // 设置食物的行位置
	food.lie = y;  // 设置食物的列位置
	
	x += 2; // 更新下一次食物的行位置
	y += 2; // 更新下一次食物的列位置
}

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
	noecho(); // 禁止回显用户输入的字符
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}

// 判断坐标 (i, j) 上是否有食物
int hasFood(int i, int j)
{
	if (food.hang == i && food.lie == j) { 
		return 1; // 有食物,返回 1
	}
	return 0; // 没有食物,返回 0
}

// 绘制游戏界面
void gamePic()
{
	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 (hasSnakeNode(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 LXL11, key = %d, food.hang = %d, food.lie = %d\n", key, food.hang, food.lie); // 显示作者信息和食物位置
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
	fresh->next = NULL; // 新节点的下一个节点为空
	
	switch (dir) {
		case UP:
			fresh->hang = tail->hang - 1; // 新节点在尾部的上方
			fresh->lie = tail->lie; // 列与尾部相同
			break;
		case DOWN:
			fresh->hang = tail->hang + 1; // 新节点在尾部的下方
			fresh->lie = tail->lie; // 列与尾部相同
			break;
		case LEFT:
			fresh->hang = tail->hang; // 行与尾部相同
			fresh->lie = tail->lie - 1; // 新节点在尾部的左侧
			break;
		case RIGHT:
			fresh->hang = tail->hang; // 行与尾部相同
			fresh->lie = tail->lie + 1; // 新节点在尾部的右侧
			break;
	}
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 初始化蛇
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 = 2; // 头节点的行位置
	head->lie = 1; // 头节点的列位置
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	// 初始时蛇的长度为4,添加节点
	addNode();
	addNode();
	addNode();
	addNode();
}

// 删除蛇的节点(尾节点)
void *deleNode()
{
	struct Snake *p;
	p = head;
	head = head->next;
	
	free(p); // 释放节点内存
}

// 移动蛇
void *moveSnake()
{
	addNode(); // 添加节点
	
	if (hasFood(tail->hang, tail->lie)) { // 如果蛇吃到了食物
		initFood(); // 重新生成食物
	} else {
		deleNode(); // 否则删除尾节点,蛇尾移动
	}
	
	// 如果蛇的尾部碰到边界,重新初始化蛇
	if (tail->hang == 0 || tail->lie == 0 || tail->hang == 20 || tail->lie == 20) {
		initSnake();
	}
}

// 刷新界面的线程函数
void *refreshJieMian(void *arg)
{
	while (1) {
		moveSnake(); // 移动蛇
		gamePic(); // 绘制游戏界面
		refresh(); // 刷新屏幕
		
		usleep(100000); // 线程休眠100毫秒
	}
}

// 改变蛇的移动方向的线程函数
void *changDir(void *arg)
{
	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; // 设置新的移动方向
	}
}

// 主函数
int main()
{
	initNcurse(); // 初始化 ncurses 库
	initSnake(); // 初始化蛇
	gamePic(); // 绘制游戏界面

	// 创建两个线程,分别用于刷新界面和处理用户输入
	pthread_t t1;
	pthread_t t2;
	pthread_create(&t1, NULL, refreshJieMian, NULL);
	pthread_create
6.4 贪吃蛇吃饭了(食物的位置是随机的):
#include <curses.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 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 = x;
	food.lie = y;
	
	}

// 初始化 ncurses 库
void initNcurse()
{
	initscr(); // 初始化屏幕
	keypad(stdscr, 1); // 启用键盘输入
	noecho();
}

// 判断坐标 (i, j) 上是否有蛇节点
int hasSnakeNode(int i, int j)
{
	struct Snake *p = head;

	while (p != NULL) {
		if (p->hang == i && p->lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		p = p->next; // 移动到下一个节点
	}
	return 0; // 没有蛇节点,返回 0
}
int hasFood(int i, int j)
{
	
		if (food.hang == i && food.lie == j) { 
			return 1; // 有蛇节点,返回 1
		}
		
	return 0; // 没有蛇节点,返回 0
}

// 绘制游戏界面
void gamePic()
{
	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 (hasSnakeNode(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 LXL11,key = %d,food.hang = %d,food.lie = %d\n",key,food.hang,food.lie); // 显示作者信息
		}
	}
}

// 添加蛇节点
void addNode()
{
	struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点
	
		fresh->next = NULL; // 新节点的下一个节点为空
	
	switch(dir){
		   case UP:
			   fresh->hang = tail->hang - 1; // 新节点的行与尾节点相同
			   fresh->lie = tail->lie; // 新节点的列在尾节点的右边
			   break;
			case DOWN:
			   fresh->hang = tail->hang +1; // 新节点的行与尾节点相同
			   fresh->lie = tail->lie; // 新节点的列在尾节点的右边
			   break;
			case LEFT:
			   fresh->hang = tail->hang; // 新节点的行与尾节点相同
			   fresh->lie = tail->lie-1; // 新节点的列在尾节点的右边
			   break;
			case RIGHT:
			   fresh->hang = tail->hang; // 新节点的行与尾节点相同
			   fresh->lie = tail->lie+1; // 新节点的列在尾节点的右边
			   break;
		   
		}
	
	tail->next = fresh; // 尾节点的下一个节点是新节点
	tail = fresh; // 新节点成为尾节点
}

// 初始化蛇
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 = 2; // 设置头节点的行
	head->lie = 1; // 设置头节点的列
	head->next = NULL; // 头节点的下一个节点为空

	tail = head; // 初始化时,头节点也是尾节点
	
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
	addNode(); // 添加节点
}
void* deleNode()
{
	struct Snake *p;
	p = head;
	head = head->next;
	
	free(p);
}

int ifShankDie()
{
	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(ifShankDie()){
		
		initSnake();
	}
}

void* refreshJieMian(void *arg)
{
	while(1){
				moveSnake();
				gamePic();
				refresh();
				
				usleep(100000);
				
		}
}
void turn(int direction)
{
	if(abs(dir) != abs(direction)){
		dir = direction;
	}
	
}

void* changDir(void *arg)
{
	
	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()
{
	
	initNcurse(); // 初始化 ncurses
	 // 初始化蛇
	initSnake();
	gamePic(); // 绘制游戏界面

	
	pthread_t t1;
	pthread_t t2;
	
	pthread_create(&t1,NULL,refreshJieMian,NULL);
	pthread_create(&t2,NULL,changDir,NULL);
	
	while(1);
		
	getch(); // 等待用户输入
	endwin(); // 结束 ncurses 模式
	
	return 0;

}

rand() 函数是C标准库 <stdlib.h> 中提供的伪随机数生成函数。它的工作原理是基于一个种子值(seed),通过一定的算法生成一系列看似随机的整数值。具体来说:

  1. 种子的设定: 在程序运行时,可以通过 srand(seed) 函数设置种子。如果不手动设置种子,则默认使用一个常量种子值。
  2. 随机数的生成: 一旦设置了种子,调用 rand() 函数就会生成下一个伪随机数。每次调用 rand(),它都会返回一个介于 0RAND_MAX 之间的整数,其中 RAND_MAX 是一个常量,代表随机数的最大可能值。
  3. 生成特定范围的随机数: 如果想要生成特定范围内的随机数,可以使用取余运算来限制范围。例如,rand() % 20 就可以得到一个介于 019(包括 019)之间的随机整数。

在你的代码中,int x = rand() % 20;int y = rand() % 20; 就是利用 rand() 函数生成两个介于 019 之间的随机整数,用来设定食物的初始位置。

七、项目代码

,使用了C语言的ncurses库来进行界面绘制和键盘输入的处理,并利用pthread库实现了多线程处理游戏逻辑和用户输入。下面是每一部分代码的中文解释:

1. 头文件和宏定义部分
#include <curses.h>
#include <stdlib.h>   
#include <unistd.h>
#include <pthread.h>

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

这部分包括所需的头文件和定义了四个方向的宏。

2. 蛇的结构体定义
struct Snake
{
    int hang; // 蛇节点的行
    int lie;  // 蛇节点的列
    struct Snake *next; // 指向下一个节点的指针
};

定义了蛇的结构体,包括行、列和指向下一个节点的指针。

3. 全局变量定义
struct Snake *head = NULL; // 蛇头指针
struct Snake *tail = NULL; // 蛇尾指针
int key; // 用户输入的键值
int dir; // 当前移动方向
struct Snake food; // 食物节点

定义了蛇的头尾指针、用户输入的键值、当前移动方向和食物节点。

4. 初始化食物函数
void initFood()
{
    int x = rand() % 20; // 随机生成食物的行
    int y = rand() % 20; // 随机生成食物的列

    food.hang = x; // 设置食物的行
    food.lie = y; // 设置食物的列
}

随机生成食物的位置,并设置食物节点的行列。

5. 初始化ncurses库函数
void initNcurse()
{
    initscr(); // 初始化屏幕
    keypad(stdscr, 1); // 启用键盘输入
    noecho(); // 不显示键盘输入的字符
}

初始化ncurses库,启用键盘输入,并设置不回显输入字符。

6. 判断蛇节点和食物是否在指定位置的函数
int hasSnakeNode(int i, int j)
{
    struct Snake *p = head;

    while (p != NULL) {
        if (p->hang == i && p->lie == j) { 
            return 1; // 有蛇节点,返回 1
        }
        p = p->next; // 移动到下一个节点
    }
    return 0; // 没有蛇节点,返回 0
}

int hasFood(int i, int j)
{
    if (food.hang == i && food.lie == j) { 
        return 1; // 有食物,返回 1
    }
    return 0; // 没有食物,返回 0
}

用于判断指定位置是否有蛇节点或食物。

7. 绘制游戏界面函数
void gamePic()
{
    int hang;
    int lie;

    move(0,0); // 将光标移动到屏幕的第一行第一列

    for (hang = 0; hang < 20; hang++) {
        if (hang == 0 || hang == 19) {
            // 绘制顶部和底部边框
            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 (hasSnakeNode(hang, lie)) {
                    printw("[]"); // 绘制蛇节点
                } else if (hasFood(hang, lie)) {
                    printw("##"); // 绘制食物
                } else {
                    printw("  "); // 绘制空白区域
                }
            }
            printw("\n");
        }
        if (hang == 19) {
            printw("by LXL11, key = %d, food.hang = %d, food.lie = %d\n", key, food.hang, food.lie); // 显示作者信息和调试信息
        }
    }
}

绘制游戏界面,包括蛇、食物、边框以及调试信息。

8. 添加蛇节点函数
void addNode()
{
    struct Snake *fresh = (struct Snake *)malloc(sizeof(struct Snake)); // 分配内存给新节点

    fresh->next = NULL; // 新节点的下一个节点为空

    switch (dir) {
        case UP:
            fresh->hang = tail->hang - 1;
            fresh->lie = tail->lie;
            break;
        case DOWN:
            fresh->hang = tail->hang + 1;
            fresh->lie = tail->lie;
            break;
        case LEFT:
            fresh->hang = tail->hang;
            fresh->lie = tail->lie - 1;
            break;
        case RIGHT:
            fresh->hang = tail->hang;
            fresh->lie = tail->lie + 1;
            break;
    }

    tail->next = fresh; // 尾节点指向新节点
    tail = fresh; // 新节点成为尾节点
}

根据当前方向向蛇身添加新节点。

9. 初始化蛇函数
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 = 2; // 设置头节点的初始行
    head->lie = 1; // 设置头节点的初始列
    head->next = NULL; // 头节点的下一个节点为空

    tail = head; // 头节点也是尾节点

    // 添加初始长度的蛇身体节点
    addNode();
    addNode();
    addNode();
    addNode();
}

初始化蛇,包括释放之前的蛇节点、初始化食物、设置初始头节点位置和添加初始长度的蛇身体节点。

10. 删除蛇节点函数
void *deleNode()
{
    struct Snake *p;
    p = head;
    head = head->next;
    free(p);
}

删除蛇的节点,用于蛇移动时的尾部节点删除。

11. 判断蛇是否死亡函数
int ifShankDie()
{
    struct Snake *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;
}

判断蛇是否撞墙或者撞到自己的身体,如果是则返回1,否则返回0。

12. 蛇移动函数
void *moveSnake()
{
    addNode(); // 添加新节点

    if (hasFood(tail->hang, tail->lie)) {
        initFood(); // 如果吃到食物则重新生成食物
    } else {
        deleNode(); // 否则删除尾节点
    }

    if (ifShankDie()) {
        initSnake(); // 如果蛇死亡则重新初始化蛇
    }
}

蛇的移动逻辑,包括添加新节点、吃到食物则重新生成食物、判断蛇是否死亡。

13. 刷新界面线程函数
void *refreshJieMian(void *arg)
{
    while (1) {
        moveSnake(); // 调用
        gamePic(); // 绘制游戏界面
		refresh(); // 刷新屏幕
		
		usleep(100000); // 线程休眠100毫秒
	}
}
  • 功能:该函数作为一个线程运行,负责不断刷新游戏界面,包括移动蛇、绘制游戏界面并刷新屏幕。
  • 细节
    • moveSnake():根据当前设定的移动方向,更新蛇的位置。
    • gamePic():根据蛇的当前位置和食物位置,绘制游戏界面。
    • refresh():将绘制好的界面刷新到屏幕上。
    • usleep(100000):线程休眠100毫秒,控制界面刷新的速度。
14. 改变蛇的移动方向的线程函数
void *changDir(void *arg)
{
    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;
        }
    }	
}
  • 功能:该函数作为另一个线程运行,不断监听用户的键盘输入,并根据输入改变蛇的移动方向。
  • 细节
    • getch():从键盘获取用户的按键值。
    • turn(direction):根据获取的按键值,调用turn函数改变蛇的移动方向。
15. 改变蛇的移动方向函数
void turn(int direction)
{
    if (abs(dir) != abs(direction)) {
        dir = direction; // 设置新的移动方向
    }
}
  • 功能:根据传入的方向参数,改变全局变量dir来控制蛇的移动方向。
  • 细节
    • abs(dir) != abs(direction):确保新的移动方向不是当前方向的相反方向,避免蛇反向移动。
16. 主函数

int main()
{
    initNcurse(); // 初始化 ncurses 库
    initSnake(); // 初始化蛇
    gamePic(); // 绘制游戏界面

    // 创建两个线程,分别用于刷新界面和处理用户输入
    pthread_t t1;
    pthread_t t2;
    pthread_create(&t1, NULL, refreshJieMian, NULL);
    pthread_create(&t2, NULL, changDir, NULL);

    while (1)
        ;

    getch(); // 等待用户输入
    endwin(); // 结束 ncurses 模式

    return 0;
}
  • 功能
    • initNcurse():初始化ncurses库,准备进行终端界面控制。
    • initSnake():初始化贪吃蛇的初始状态,包括蛇的位置和食物的位置。
    • gamePic():绘制游戏的初始界面。
    • 创建两个线程t1t2
      • t1线程运行refreshJieMian函数,不断刷新游戏界面。
      • t2线程运行changDir函数,监听用户的键盘输入并改变蛇的移动方向。
    • while (1)循环保持程序运行,直到用户通过键盘输入结束游戏。
    • getch():等待用户输入,防止程序退出。
    • endwin():结束ncurses模式,释放资源并退出程序。
  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值