- 基于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():
- initscr();:
-
- initscr() 是 ncurses 库的初始化函数。调用这个函数后,终端将进入 ncurses 模式,程序可以使用 ncurses 的各种功能来控制终端显示和处理输入。
- 初始化 ncurses 库。这是使用 ncurses 的第一步,必须调用这个函数来设置库内部的数据结构,并准备终端窗口进行操作。
- printw("this is a curses window.\n");:
-
- 在当前窗口中打印字符串 "this is a curses window.\n"。printw 函数类似于标准的 printf,但用于 ncurses 窗口。
- getch();:
-
- 等待用户输入一个字符。程序在这里暂停,直到用户按下一个键。这样可以让用户有时间看到打印的字符串。
- endwin();:
-
- 结束 ncurses 模式,恢复终端的正常操作状态。调用这个函数后,终端会返回到普通模式,之前由 ncurses 修改的终端设置都会恢复。
- 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;
}
- refresh() 函数:
-
- 在使用
ncurses
库时,屏幕的输出并不会实时更新,而是在程序中调用refresh()
函数时才会将缓冲区中的内容刷新到屏幕上。 - 在你的游戏中,
gamePic()
函数用来绘制游戏界面,但绘制完后需要调用refresh()
函数来将界面显示出来,否则用户看不到更新的内容。
- 在使用
- 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)
分别计算了变量a
和b
的绝对值。printf
函数的格式化字符串被调整,确保输出结果的格式正确。
2. 贪吃蛇绝对值应用
void turn(int direction)
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
用来检查两个方向变量 dir
和 direction
的绝对值是否相同,如果不相同,则将 dir
设置为 direction
的值。
具体解释如下:
-
- abs 函数:
abs()
是 C 标准库<stdlib.h>
中的函数,用来返回一个整数的绝对值。 - 逻辑判断:表达式
abs(dir) != abs(direction)
检查了dir
和direction
的绝对值是否相同。
- abs 函数:
-
-
- 如果
dir
和direction
的绝对值相同,表达式的值为假 (false),不执行后续操作。 - 如果
dir
和direction
的绝对值不同,表达式的值为真 (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),通过一定的算法生成一系列看似随机的整数值。具体来说:
- 种子的设定: 在程序运行时,可以通过
srand(seed)
函数设置种子。如果不手动设置种子,则默认使用一个常量种子值。 - 随机数的生成: 一旦设置了种子,调用
rand()
函数就会生成下一个伪随机数。每次调用rand()
,它都会返回一个介于0
到RAND_MAX
之间的整数,其中RAND_MAX
是一个常量,代表随机数的最大可能值。 - 生成特定范围的随机数: 如果想要生成特定范围内的随机数,可以使用取余运算来限制范围。例如,
rand() % 20
就可以得到一个介于0
到19
(包括0
和19
)之间的随机整数。
在你的代码中,int x = rand() % 20;
和 int y = rand() % 20;
就是利用 rand()
函数生成两个介于 0
到 19
之间的随机整数,用来设定食物的初始位置。
七、项目代码
,使用了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()
:绘制游戏的初始界面。- 创建两个线程
t1
和t2
:
-
-
t1
线程运行refreshJieMian
函数,不断刷新游戏界面。t2
线程运行changDir
函数,监听用户的键盘输入并改变蛇的移动方向。
-
-
while (1)
循环保持程序运行,直到用户通过键盘输入结束游戏。getch()
:等待用户输入,防止程序退出。endwin()
:结束ncurses模式,释放资源并退出程序。