当前位置: 首页 > news >正文

【C语言】贪吃蛇 详解

该项目需要的技术要点

C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等。

由于篇幅限制 和 使知识模块化,

若想了解 使用到的 Win32API 的知识:请点击跳转:【Win32API】贪吃蛇会使用到的 Win32API

目录

1. 贪吃蛇游戏设计与分析

1.0 贪吃蛇页面大纲

1.1 地图

1.1.1 控制台窗口的坐标知识

1.1.2 宽字符:

1.1.3 地图坐标

1.2 蛇身和食物

1.3 数据结构设计

1.4 整个游戏流程设计

2. 核⼼逻辑实现分析

2.1 游戏主逻辑 

2.2 游戏开始

2.3 游戏运行

2.4 游戏结束

3.总代码概览

Snake.c

Snake.h

 test.c



准备工作:创建三个文件

1. 贪吃蛇游戏设计与分析

1.0 贪吃蛇页面大纲

 

 我们最终的贪吃蛇大纲要是这个样子,那我们的地图如何布置呢?

1.1 地图

这里不得不讲一下控制台窗口的一些知识

1.1.1 控制台窗口的坐标知识

控制台窗口的坐标如下所示,横向的是X轴,从左向右依次增⻓,纵向是Y轴,从上到下依次增⻓。

1.1.2 宽字符:

在游戏地图上,我们打印墙体使用宽字符:□,打印蛇使用宽字符 ● ,打印食物使用宽字符★或图中的那个
普通的字符是占一个字节的,这类宽字符是占用 2 个字节。

宽字符的来源
过去C语言并不适合非英语国家(地区)使用。
C语言最初假定字符都是自己的。但是这些假定并不是在世界的任何地方都适用。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入和宽字符的类型 wchar_t 和宽字符的输入和输出函数,加入和 <locale.h> 头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。
(1) <locale.h>本地化
<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。

(2) setlocale函数

因为各个地区的代码语言环境不同:需要转换到 本地环境, 才能支持宽字符(如汉字)的输出

用 "" 作为第 2 个参数,(注意:就是一对双引号,中间没有空格) ,调用 setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。
#include<locale.h> // 包含头文件
setlocale(LC_ALL, "" );//切换到本地环境:中文环境

(3)宽字符 的 打印格式
宽字符要 在单引号前面加上 L,表示宽字符,宽字符的打印用wprintf,对应wprintf()的占位符为%lc;

假设 A 为一个窄字符,B 为 宽字符

窄字符:printf("%c", A)宽字符:wprintf(L"%lc", B);
从输出的结果来看,我们发现一个普通字符占一个字符的位置
但是打印一个宽字符,占用 2 个字符的位置,那么我们如果
要在贪吃蛇中使用宽字符,就得处理好地图上坐标的计算。

下图,边框上的墙体就是 一种宽字符:□ ,宽度占 2 个字符位

1个坐标可以放1个正常字符,即窄字符

2个坐标可以存放1个宽字符 

1.1.3 地图坐标

我们假设实现一个棋盘 27 行, 58 列的棋盘(行和列可以根据自己的情况修改),再围绕地图画出墙,因为 宽字符的原因:最好 是    2 * 行数  = 列数
如下:
void CreateMap()
{// 上:29个宽格子SetPos(0, 0);// 从 (0, 0)开始for (int i = 0; i <= 56; i += 2) // 每隔两格打印一个 宽字符{wprintf(L"%c", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2){wprintf(L"%c", WALL);}// 打印 左右  坐标需要跟着不断变化 :因为 默认打印是 横着打印的//左for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%c", WALL);}//右for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%c", WALL);}
}

1.2 蛇身和食物

初始化状态,假设蛇的长度是 5 ,蛇身的每个节点是●,在固定的一个坐标处,比如 (24,5) 处开始出现蛇,连续 5 个节点。 注意: 蛇的每个节点的 x 坐标必须是 2 的倍数, 即偶数,否则撞墙时可能会出现蛇的一个节点有一半出现在墙体中,另外一半在墙外的现象,坐标不好对齐。
关于食物,就是在墙体内随机生成一个坐标(x坐标必须是 2 的倍数)坐标不能和蛇的身体重合,然后打印★。

void InitSnake(pSnake ps)
{// 创建5个节点: pSnakeNode cur = NULL;for (int i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake:malloc");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;// 头插法:// 没有节点下if (ps->pSnake == NULL) // pSnake 是 指向蛇头 的指针:即 单链表中 的头指针{ps->pSnake = cur;}// 已经有节点  :原本不是有  ps->pSnake == cur; pSnake 存放着 上一个节点的地址,借这个来 链接下一个节点,然后更新 pSnake, 最后 pSnake 一直保持指向蛇头else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇的身体// 打印 蛇身: 找到蛇头指针,循环遍历 打印相应坐标 就行// 注意:这个 cur 和上面的 cur 含义 不同,其实是 临时变量的 意思cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 初始化贪吃蛇的其他信息ps->dir = RIGHT;  // 初始默认向右ps->FoodWeight = 10;ps->pFood = NULL;ps->Score = 0;ps->SleepTime = 200; // 休眠 200 msps->state = OK;
}

1.3 数据结构设计

在游戏运行的过程中,蛇每次吃一个⻝物,蛇的身体就会变⻓一节,如果我们使用 链表 存储蛇的信息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,
所以蛇节点结构如下:
// 蛇身节点
// 蛇身用链表来维护:一个蛇身节点就是一个链表节点
// 蛇在行动时,蛇身的每一个节点的坐标都在变化,所以要不断记录下来
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
// 创建一个指向蛇身节点的结构体指针

要管理整条贪吃蛇,我们再封装一个Snake的结构来维护整条贪吃蛇:

//贪吃蛇
// 整条蛇
// 要管理整条贪吃蛇当前状态过程的信息,我们再封装一个Snake的结构来维护整条贪吃蛇:
typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针:指向蛇头pSnakeNode pFood;//维护食物的指针:食物实际上也是蛇身节点,只是打印不一样enum DIRECTION dir;//蛇头的方向默认是向右enum GAME_STATUS state;//维护游戏进行状态:蛇身撞墙、吃到自己、手动退出(上面定义了一个枚举类型)int Score;//当前获得分数int FoodWeight;//默认每个食物 10 分int SleepTime;//每走一步休眠时间:控制蛇移动的速度(本质:蛇身在移动过程中 可以发现,蛇身节点在 一闪一闪的停顿,其实是Sleep 控制睡眠时间,来控制总体移动速度)
}Snake, * pSnake;  // pSnake 是 指向贪吃蛇的指针: 下面就用上了

蛇的方向:可以一一列举,使用枚举


//蛇走的方向
enum DIRECTION
{UP = 1, // 上DOWN, // 下LEFT, // 左RIGHT // 右
};

游戏状态:可以一一列举,使用枚举

/蛇的状态:  维护游戏状态的 枚举类型
enum GAME_STATE
{OK,//正常运行ESC, // 按 ESC 键退出KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到自己//END_NOMAL//正常结束
};

1.4 整个游戏流程设计

2. 核⼼逻辑实现分析

2.1 游戏主逻辑 

GameStart(&snake);   游戏开始
GameRun(&snake);    游戏过程
GameEnd(&snake);    游戏结束

#include"snake.h"void test()
{int ch = 0;do{// 创建一条贪吃蛇Snake snake = { 0 };// 把蛇传过去:因为蛇移动时,就是变化的过程,则应该传地址GameStart(&snake); // 游戏开始前的初始化:按键功能提示、游戏界面打印、墙体初始化等等GameRun(&snake); // 游戏过程GameEnd(&snake);//善后工作SetPos(20, 15);printf("再来一局?(y/n)");ch = getchar();getchar();} while (ch == 'Y' || ch == 'y');}
int main()
{//适配本地中文环境setlocale(LC_ALL, "");//贪吃蛇游戏的设置test();return 0;
}

2.2 游戏开始

    一、打印欢迎信息
    WelcomeToGame();
    二、绘制地图
    CreateMap(); 
    三、初始化蛇
    InitSnake(ps); 
    四、创建食物
    CreateFood(ps);

// 游戏前准备
void GameStart(pSnake ps)
{// 一、设置控制台的信息:窗口大小、窗口名// 设置控制台窗口的长宽:设置控制台窗口的大小// 因为光是墙体就 27行 58列 了,界面右侧还要 写一些介绍指引,则干脆  列为 100, 行 30 system("mode con cols=100 lines=30");system("title 贪吃蛇");// 设置cmd窗口名称 // 二、隐藏光标:不想光标在一直闪HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//隐藏光标操作: 6.5.1节 那里有示例CONSOLE_CURSOR_INFO cursorinfo; //使用 6.5.1 的结构体,定义一个结构体变量,名为 CursorInfoGetConsoleCursorInfo(handle, &cursorinfo); //???? 为什么没有这句话还不行了??获取控制台光标信息,若不想查看也可以不写此句//先修改成员变量,再传进Set函数中:句柄 + 光标结构体,相当于对 句柄所指定的控制台窗口,就行光标修改设置cursorinfo.bVisible = false;  //隐藏控制台光标SetConsoleCursorInfo(handle, &cursorinfo); //设置控制台光标状态//一、打印欢迎信息WelcomeToGame();//二、绘制地图CreateMap(); //三、初始化蛇InitSnake(ps); // 要把 蛇 的结构体的传过去,才能修改蛇的状态信息//四、创建食物CreateFood(ps); // 因为 食物也是 链表节点,最后要链接到 蛇身链表上的,因此涉及到 蛇身的修改,要传入蛇的头指针
}

游戏前准备  GameStart(pSnake ps)   的 四个子函数

先封装 设置坐标函数

//封装一个设置光标位置的函数 SetPos:
void SetPos(int x, int y)
{//获取标准输出设备的句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posCOORD pos = { x,y };SetConsoleCursorPosition(handle, pos);
}

 一、打印欢迎信息


void WelcomeToGame()
{//一、 欢迎页面// 1、改变打印光标的坐标: 要处在 窗口中央,前面设置窗口 : 列为 100, 行 30 SetPos(38, 13);  // 坐标的设置 看感觉来吧 printf("欢迎来到贪吃蛇小游戏\n");// 打印完上面这句后:会有一句:请按任意键继续. . .     位置不好看,可以再次设置光标位置:其实本质上就是一个新的光标SetPos(38, 16);  // 请按任意键继续. . .     的位置system("pause"); //一张页面打印完成后 暂停一下:请按任意键继续. . .    然后打印另一张// 清空屏幕信息:要打印下一张页面system("cls");//二、功能介绍页面SetPos(29, 8);printf("1、用  ↑   ↓   ←    →  来控制蛇的移动  \n");SetPos(29, 10);printf("2、F3 是 加速,F4 是 减速     O(∩_∩)O\n");SetPos(29, 12);printf("3、加速可以获得更高的分数    (づ ̄ 3 ̄)づ\n");SetPos(38, 16);  // 请按任意键继续. . .     的位置system("pause"); //一张页面打印完成后 暂停一下// 清空屏幕信息:要打印下一张页面system("cls");
}

 

 二、绘制地图


void CreateMap()
{// 上:29个宽格子SetPos(0, 0);// 从 (0, 0)开始for (int i = 0; i <= 56; i += 2) // 每隔两格打印一个 宽字符{wprintf(L"%c", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2){wprintf(L"%c", WALL);}// 打印 左右  坐标需要跟着不断变化 :因为 默认打印是 横着打印的//左for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%c", WALL);}//右for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%c", WALL);}
}

三、初始化 蛇

// 其实,再屏幕上面显示出来相应坐标的 打印数据,本质是要 定位到 相应坐标,SetPos 函数 很重要
// 蛇最开始⻓度为5节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。
// 创建5个节点,然后将每个节点存放在链表中进⾏管理。
void InitSnake(pSnake ps)
{// 创建5个节点: pSnakeNode cur = NULL;for (int i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake:malloc");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;// 头插法:// 没有节点下if (ps->pSnake == NULL) // pSnake 是 指向蛇头 的指针:即 单链表中 的头指针{ps->pSnake = cur;}// 已经有节点  :原本不是有  ps->pSnake == cur; pSnake 存放着 上一个节点的地址,借这个来 链接下一个节点,然后更新 pSnake, 最后 pSnake 一直保持指向蛇头else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇的身体// 打印 蛇身: 找到蛇头指针,循环遍历 打印相应坐标 就行// 注意:这个 cur 和上面的 cur 含义 不同,其实是 临时变量的 意思cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 初始化贪吃蛇的其他信息ps->dir = RIGHT;  // 初始默认向右ps->FoodWeight = 10;ps->pFood = NULL;ps->Score = 0;ps->SleepTime = 200; // 休眠 200 msps->state = OK;
}

 四、创建食物


// 思路:
// 1、食物随机出现,就是 随机一个坐标打印,难点:运算 随机数的取模
// 2、x 的范围:2 ~ 54    ---> 生成 0 ~ 52   + 2    ---> rand()%53  + 2    因为 x 是占 2 个窄字符的, x  的取值必须是 偶数,不然蛇 吃 不全,即 不能全覆盖
// 3、y 的范围:1 ~ 25    ---> 生成 0 ~ 24   + 2    ---> rand()%25 + 1
// 创建食物注意事项
// 1.食物是随机出现的,坐标就是随机的
// 2.坐标必须在墙内
// 3.坐标不能在蛇的身体上
void CreateFood(pSnake ps)
{// 1.食物是随机出现的,坐标就是随机的// 2.坐标必须在墙内int x = 0;int y = 0;again:do{x = rand() % 53 + 2;y = rand() % 24 + 1;} while (x % 2 != 0);  // 这一步啥意思呀:42min 左右// 3.坐标不能在蛇的身体上:遍历蛇身一一对比对比//判断食物是否在蛇身上pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//创建食物: 一个新节点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//  (pSnakeNode) 和 (SnakeNode)  千万别混淆!!!if (pFood == NULL){perror("CreateFood():malloc()");return;}pFood->x = x;pFood->y = y;ps->pFood = pFood; // pFood 是 贪吃蛇 结构体中的成员变量, pfood 是新创建的新食物节点// 思路:将食物放进 贪吃蛇 结构体中,判定重合后,就头插法,没有就一直 移动//打印食物SetPos(x, y);wprintf(L"%c", FOOD);//getchar(); // 注意 :这个别忘了关闭
}

2.3 游戏运行

游戏运行期间,右侧打印帮助信息,提示玩家
根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。
如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。

确定了蛇的方向和速度,蛇就可以移动了。

 

// 游戏运行过程
void GameRun(pSnake ps)
{// 一、打印界面右侧的帮助提示信息PrintHelpInfo();// 游戏一般 是 do while循环:总之都需要先让游戏先运行一次do{//当前的分数情况: 打印不断变化的分数技巧:不断更新 蛇节点的 Score 更新就打印SetPos(65, 4);//[BACKGROUND代表背景:就是背景;FOREGROUND代表前景:就是字体颜色 【http://t.csdnimg.cn/VFHPV】// SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),  FOREGROUND_RED | BACKGROUND_INTENSITY);CONSOLE_FONT_INFOEX cfi;cfi.cbSize = sizeof cfi;cfi.dwFontSize.X = 0; //字宽cfi.dwFontSize.Y = 20;//字高cfi.FontWeight = FW_NORMAL;//粗细SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);printf("总分:%5d", ps->Score);SetPos(65, 6 );printf("食物的分值:%02d", ps->FoodWeight);// 二、检测按键:判断发出的指令:使用 那个判断虚拟键值的函数// 上下左右,ESC,空格,F3,F4// 不能同时 方向 等于 相反方向if (KEY_PRESS(VK_UP) && ps->dir != DOWN)// 上 :正在向上,则同时 蛇 的方向不能等于  DOWN !!!!{ps->dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->dir != UP){ps->dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT){ps->dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT){ps->dir = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){ps->state = ESC; // 将状态该成 退出状态break;}else if (KEY_PRESS(VK_SPACE)) // 空格 :(VK_SPACE){//游戏要暂定// 封装一个函数 Pause() : 暂定和回复暂定,总不能一直暂停下去呀Pause();//暂定和回复暂定}else if (KEY_PRESS(VK_F3)) // 加速即休眠时间 变短,同时分数变多,而且 次数有限制{if (ps->SleepTime >= 80) // 最低下限:SleepTime = 80 (初始化sleep 是 200ms){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)) // 减速 即休眠时间 变长,同时分数变少{if (ps->FoodWeight > 2) // 最低下限:FoodWeight > 2{ps->SleepTime += 30;ps->FoodWeight -= 2;}}// 三 四 步 别写反// 三、走一步: 每轮移动一步,要检测是否撞墙.....等状态// 蛇身移动较为复杂,封装成一个函数便于分析SnakeMove(ps);// 四、睡眠一下Sleep(ps->SleepTime);} while (ps->state == OK); // 结束条件:当游戏状态 不是 OK 时 就结束}

游戏运行过程函数:GameRun(pSnake ps) 的 几个子函数

一、PrintHelpInfo():打印界面右侧的帮助提示信息

二、Pause():游戏暂停键

三、SnakeMove(ps):蛇身移动(较为复杂)

一、PrintHelpInfo():打印界面右侧的帮助提示信息

void PrintHelpInfo()
{// 设置位置SetPos(65, 8);printf("温馨提示\n");SetPos(65, 10);printf("1、不能穿墙\n");SetPos(65, 11);printf("2、不能咬到自己\n");SetPos(65, 12);printf("3、按   ESC   退出游戏\n");SetPos(65, 13);printf("4、按   空格  暂停游戏\n");SetPos(65, 15);printf("操作回顾\n");SetPos(65, 17);printf("5、用 ↑↓ ← → 来控制蛇的移动  \n");SetPos(65, 18);printf("6、F3 加速,F4 减速\n");SetPos(65, 19);printf("7、加速可以获得更高的分数\n");SetPos(65, 22);printf("版权 @时差\n");// getchar(); // 注意 :这个别忘了关闭
}

二、Pause():游戏暂停键

void Pause()//游戏暂停
{// tip :  死循环的 sleep  , 再循环中 判断 再次点击空格键 即可回复while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}

三、SnakeMove(ps):蛇身移动(较为复杂)

分成几个子函数:

  1. NextIsFood(ps, pNext):判断蛇头 下一步处 是否是 食物
  2. EatFood(ps, pNext); 是食物就吃掉
  3. NotEatFood(ps, pNext); 不是食物就正常走一步
  4. KillByWall(ps);// 检测撞墙
  5. KillBySelf(ps);// 是否撞到自己

// 蛇身 移动 
// 移动的本质: 没吃食物,则长度不变,将  方向节点 链接到 蛇头  上,free 掉 尾节点
void SnakeMove(pSnake ps)
{// 根据按键:调整移动方向// 创建下一个节点:表示下一个方向的第一个节点:根据图 更好理解 1h43min // 取名:方向节点pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove():malloc:");return;}pNext->next = NULL;// 指向空就好// 利用 方向节点,// pSnake 是 贪吃蛇 结构体的成员变量:指向蛇头 // 通过 改变 方向节点  的坐标位置,引导蛇头的移动switch (ps->dir){case UP:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT:pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT:pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//下一个坐标处是否是食物if (NextIsFood(ps, pNext)){//是食物就吃掉:将  方向节点 链接到 蛇身上,将 食物指针 free 释放掉EatFood(ps, pNext);}else{//不是食物就正常一步: 没吃食物,则长度不变,将  方向节点 链接到 蛇头  上,free 掉 尾节点NotEatFood(ps, pNext);}// getchar(); // 检测蛇移动时,不能用 getchar 了 否则,蛇 走一步 就不走了//检测撞墙KillByWall(ps);//是否撞到自己KillBySelf(ps);
}

 1、NextIsFood(ps, pNext):判断蛇头 下一步处 是否是 食物

// 判断蛇头 下一步处 是否是 食物:就是判断 方向指针pNext 是不是 食物
int NextIsFood(pSnake ps, pSnakeNode pNext)
{// 两者坐标重合 就是了if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y) return 1;//下一个坐标处是食物else return 0;
}

 2、EatFood(ps, pNext); 是食物就吃掉

//下一步处 是食物就吃掉:将  方向节点 链接到 蛇身上(头插法),将 食物指针 free 释放掉
void EatFood(pSnake ps, pSnakeNode pNext)
{pNext->next = ps->pSnake; // 直接让 方向节点 指向头节点ps->pSnake = pNext; // 更新头指针// 打印蛇身:每次更新一轮,就打印一次// PrintSnake(ps); // 这里注意一下 是不是不能用:好像不是,改了后 依旧停留在 1.0 版本//打印蛇pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}// 分数变化:ps->Score += ps->FoodWeight;// 释放 旧食物节点:吃掉了 就不存在了free(ps->pFood);//创建新食物;CreateFood(ps);
}

3、 NotEatFood(ps, pNext); 不是食物就正常走一步

void NotEatFood(pSnake ps, pSnakeNode pNext)
{// 头插法:链接方向指针,相当于移动了一步pNext->next = ps->pSnake;// 直接让 方向节点 指向头节点ps->pSnake = pNext;// 更新头指针// 释放 尾节点,同时还要保留 尾节点 的前一个:倒数第二个:实际上 就是 单链表的 尾删法pSnakeNode cur = ps->pSnake;while (cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 将尾节点的位置  打印成 空白字符:不能 还打印蛇身// 先 打印成 空白,后再释放:别急着释放SetPos(cur->next->x, cur->next->y);printf("  ");// 注意 空格是 两个free(cur->next);cur->next = NULL;//易错// 注意前面以及在 循环遍历蛇身了:同时就可以进行 打印 蛇身节点// 打印蛇身:每次更新一轮,就打印一次// PrintSnake(ps);
}

4、 KillByWall(ps);// 检测撞墙

void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 ||ps->pSnake->x == 56 ||ps->pSnake->y == 0 ||ps->pSnake->y == 26){ps->state = KILL_BY_WALL;}
}

5、KillBySelf(ps);// 是否撞到自己

void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;while (cur){if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->state = KILL_BY_SELF;return;}cur = cur->next;}
}

2.4 游戏结束

游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇身节点。
void GameEnd(pSnake ps)
{SetPos(15, 12);switch (ps->state){case ESC:printf("主动退出游戏\n");break;case KILL_BY_WALL:printf("很遗憾,撞墙了,游戏结束\n");break;case KILL_BY_SELF:printf("很遗憾,撞到自己,游戏结束\n");break;}//释放pSnakeNode cur = ps->pSnake;pSnakeNode del = NULL;while (cur){del = cur;cur = cur->next;free(del);}free(ps->pFood);ps = NULL;}

3.总代码概览

Snake.c

#define _CRT_SECURE_NO_WARNINGS 1#include"snake.h"//封装一个设置光标位置的函数 SetPos:
void SetPos(int x, int y)
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x,y };SetConsoleCursorPosition(handle, pos);
}// 打印欢迎信息
void WelcomeToGame()
{//一、 欢迎页面// 1、改变打印光标的坐标: 要处在 窗口中央,前面设置窗口 : 列为 100, 行 30 SetPos(38, 13);  // 坐标的设置 看感觉来吧 printf("欢迎来到贪吃蛇小游戏\n");// 打印完上面这句后:会有一句:请按任意键继续. . .     位置不好看,可以再次设置光标位置:其实本质上就是一个新的光标SetPos(38, 16);  // 请按任意键继续. . .     的位置system("pause"); //一张页面打印完成后 暂停一下:请按任意键继续. . .    然后打印另一张// 清空屏幕信息:要打印下一张页面system("cls");//二、功能介绍页面SetPos(29, 8);printf("1、用  ↑   ↓   ←    →  来控制蛇的移动  \n");SetPos(29, 10);printf("2、F3 是 加速,F4 是 减速     O(∩_∩)O\n");SetPos(29, 12);printf("3、加速可以获得更高的分数    (づ ̄ 3 ̄)づ\n");SetPos(38, 16);  // 请按任意键继续. . .     的位置system("pause"); //一张页面打印完成后 暂停一下// 清空屏幕信息:要打印下一张页面system("cls");
}// 绘制地图
void CreateMap()
{// 上:29个宽格子SetPos(0, 0);// 从 (0, 0)开始for (int i = 0; i <= 56; i += 2) // 每隔两格打印一个 宽字符{wprintf(L"%c", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2){wprintf(L"%c", WALL);}// 打印 左右  坐标需要跟着不断变化 :因为 默认打印是 横着打印的//左for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%c", WALL);}//右for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%c", WALL);}
}// 其实,再屏幕上面显示出来相应坐标的 打印数据,本质是要 定位到 相应坐标,SetPos 函数 很重要
// 初始化 蛇
// 蛇最开始⻓度为5节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。
// 创建5个节点,然后将每个节点存放在链表中进⾏管理。
void InitSnake(pSnake ps)
{// 创建5个节点: pSnakeNode cur = NULL;for (int i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake:malloc");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;// 头插法:// 没有节点下if (ps->pSnake == NULL) // pSnake 是 指向蛇头 的指针:即 单链表中 的头指针{ps->pSnake = cur;}// 已经有节点  :原本不是有  ps->pSnake == cur; pSnake 存放着 上一个节点的地址,借这个来 链接下一个节点,然后更新 pSnake, 最后 pSnake 一直保持指向蛇头else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇的身体// 打印 蛇身: 找到蛇头指针,循环遍历 打印相应坐标 就行// 注意:这个 cur 和上面的 cur 含义 不同,其实是 临时变量的 意思cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 初始化贪吃蛇的其他信息ps->dir = RIGHT;  // 初始默认向右ps->FoodWeight = 10;ps->pFood = NULL;ps->Score = 0;ps->SleepTime = 200; // 休眠 200 msps->state = OK;
}// 创建食物
// 思路:
// 1、食物随机出现,就是 随机一个坐标打印,难点:运算 随机数的取模
// 2、x 的范围:2 ~ 54    ---> 生成 0 ~ 52   + 2    ---> rand()%53  + 2    因为 x 是占 2 个窄字符的, x  的取值必须是 偶数,不然蛇 吃 不全,即 不能全覆盖
// 3、y 的范围:1 ~ 25    ---> 生成 0 ~ 24   + 2    ---> rand()%25 + 1
// 创建食物注意事项
// 1.食物是随机出现的,坐标就是随机的
// 2.坐标必须在墙内
// 3.坐标不能在蛇的身体上
void CreateFood(pSnake ps)
{// 1.食物是随机出现的,坐标就是随机的// 2.坐标必须在墙内int x = 0;int y = 0;again:do{x = rand() % 53 + 2;y = rand() % 24 + 1;} while (x % 2 != 0);  // 这一步啥意思呀:42min 左右// 3.坐标不能在蛇的身体上:遍历蛇身一一对比对比//判断食物是否在蛇身上pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//创建食物: 一个新节点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//  (pSnakeNode) 和 (SnakeNode)  千万别混淆!!!if (pFood == NULL){perror("CreateFood():malloc()");return;}pFood->x = x;pFood->y = y;ps->pFood = pFood; // pFood 是 贪吃蛇 结构体中的成员变量, pfood 是新创建的新食物节点// 思路:将食物放进 贪吃蛇 结构体中,判定重合后,就头插法,没有就一直 移动//打印食物SetPos(x, y);wprintf(L"%c", FOOD);//getchar(); // 注意 :这个别忘了关闭
}// 游戏前准备
void GameStart(pSnake ps)
{// 一、设置控制台的信息:窗口大小、窗口名// 设置控制台窗口的长宽:设置控制台窗口的大小// 因为光是墙体就 27行 58列 了,界面右侧还要 写一些介绍指引,则干脆  列为 100, 行 30 system("mode con cols=100 lines=30");system("title 贪吃蛇");// 设置cmd窗口名称 // 二、隐藏光标:不想光标在一直闪HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//隐藏光标操作: 6.5.1节 那里有示例CONSOLE_CURSOR_INFO cursorinfo; //使用 6.5.1 的结构体,定义一个结构体变量,名为 CursorInfoGetConsoleCursorInfo(handle, &cursorinfo); //???? 为什么没有这句话还不行了??获取控制台光标信息,若不想查看也可以不写此句//先修改成员变量,再传进Set函数中:句柄 + 光标结构体,相当于对 句柄所指定的控制台窗口,就行光标修改设置cursorinfo.bVisible = false;  //隐藏控制台光标SetConsoleCursorInfo(handle, &cursorinfo); //设置控制台光标状态//打印欢迎信息WelcomeToGame();//绘制地图CreateMap(); //初始化蛇InitSnake(ps); // 要把 蛇 的结构体的传过去,才能修改蛇的状态信息//创建食物CreateFood(ps); // 因为 食物也是 链表节点,最后要链接到 蛇身链表上的,因此涉及到 蛇身的修改,要传入蛇的头指针
}void PrintHelpInfo()
{// 设置位置SetPos(65, 8);printf("温馨提示\n");SetPos(65, 10);printf("1、不能穿墙\n");SetPos(65, 11);printf("2、不能咬到自己\n");SetPos(65, 12);printf("3、按   ESC   退出游戏\n");SetPos(65, 13);printf("4、按   空格  暂停游戏\n");SetPos(65, 15);printf("操作回顾\n");SetPos(65, 17);printf("5、用 ↑↓ ← → 来控制蛇的移动  \n");SetPos(65, 18);printf("6、F3 加速,F4 减速\n");SetPos(65, 19);printf("7、加速可以获得更高的分数\n");SetPos(65, 22);printf("版权 @时差\n");// getchar(); // 注意 :这个别忘了关闭
}// 按 空格  :暂定和回复暂定
void Pause()//游戏暂停
{// tip :  死循环的 sleep  , 再循环中 判断 再次点击空格键 即可回复while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}// 判断蛇头 下一步处 是否是 食物:就是判断 方向指针pNext 是不是 食物
int NextIsFood(pSnake ps, pSnakeNode pNext)
{// 两者坐标重合 就是了if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y) return 1;//下一个坐标处是食物else return 0;
}//下一步处 是食物就吃掉:将  方向节点 链接到 蛇身上(头插法),将 食物指针 free 释放掉
void EatFood(pSnake ps, pSnakeNode pNext)
{pNext->next = ps->pSnake; // 直接让 方向节点 指向头节点ps->pSnake = pNext; // 更新头指针// 打印蛇身:每次更新一轮,就打印一次// PrintSnake(ps); // 这里注意一下 是不是不能用:好像不是,改了后 依旧停留在 1.0 版本//打印蛇pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}// 分数变化:ps->Score += ps->FoodWeight;// 释放 旧食物节点:吃掉了 就不存在了free(ps->pFood);//创建新食物;CreateFood(ps);
}void NotEatFood(pSnake ps, pSnakeNode pNext)
{// 头插法:链接方向指针,相当于移动了一步pNext->next = ps->pSnake;// 直接让 方向节点 指向头节点ps->pSnake = pNext;// 更新头指针// 释放 尾节点,同时还要保留 尾节点 的前一个:倒数第二个:实际上 就是 单链表的 尾删法pSnakeNode cur = ps->pSnake;while (cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 将尾节点的位置  打印成 空白字符:不能 还打印蛇身// 先 打印成 空白,后再释放:别急着释放SetPos(cur->next->x, cur->next->y);printf("  ");// 注意 空格是 两个free(cur->next);cur->next = NULL;//易错// 注意前面以及在 循环遍历蛇身了:同时就可以进行 打印 蛇身节点// 打印蛇身:每次更新一轮,就打印一次// PrintSnake(ps);
}void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 ||ps->pSnake->x == 56 ||ps->pSnake->y == 0 ||ps->pSnake->y == 26){ps->state = KILL_BY_WALL;}
}void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;while (cur){if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->state = KILL_BY_SELF;return;}cur = cur->next;}
}// 蛇身 移动 
// 移动的本质: 没吃食物,则长度不变,将  方向节点 链接到 蛇头  上,free 掉 尾节点
void SnakeMove(pSnake ps)
{// 根据按键:调整移动方向// 创建下一个节点:表示下一个方向的第一个节点:根据图 更好理解 1h43min // 取名:方向节点pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove():malloc:");return;}pNext->next = NULL;// 指向空就好// 利用 方向节点,// pSnake 是 贪吃蛇 结构体的成员变量:指向蛇头 // 通过 改变 方向节点  的坐标位置,引导蛇头的移动switch (ps->dir){case UP:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT:pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT:pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//下一个坐标处是否是食物if (NextIsFood(ps, pNext)){//是食物就吃掉:将  方向节点 链接到 蛇身上,将 食物指针 free 释放掉EatFood(ps, pNext);}else{//不是食物就正常一步: 没吃食物,则长度不变,将  方向节点 链接到 蛇头  上,free 掉 尾节点NotEatFood(ps, pNext);}// getchar(); // 检测蛇移动时,不能用 getchar 了 否则,蛇 走一步 就不走了//检测撞墙KillByWall(ps);//是否撞到自己KillBySelf(ps);
}// 游戏运行过程
void GameRun(pSnake ps)
{// 一、打印界面右侧的帮助提示信息PrintHelpInfo();// 游戏一般 是 do while循环:总之都需要先让游戏先运行一次do{//当前的分数情况: 打印不断变化的分数技巧:不断更新 蛇节点的 Score 更新就打印SetPos(65, 4);//[BACKGROUND代表背景:就是背景;FOREGROUND代表前景:就是字体颜色 【http://t.csdnimg.cn/VFHPV】// SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),  FOREGROUND_RED | BACKGROUND_INTENSITY);CONSOLE_FONT_INFOEX cfi;cfi.cbSize = sizeof cfi;cfi.dwFontSize.X = 0; //字宽cfi.dwFontSize.Y = 20;//字高cfi.FontWeight = FW_NORMAL;//粗细SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);printf("总分:%5d", ps->Score);SetPos(65, 6 );printf("食物的分值:%02d", ps->FoodWeight);// 二、检测按键:判断发出的指令:使用 那个判断虚拟键值的函数// 上下左右,ESC,空格,F3,F4// 不能同时 方向 等于 相反方向if (KEY_PRESS(VK_UP) && ps->dir != DOWN)// 上 :正在向上,则同时 蛇 的方向不能等于  DOWN !!!!{ps->dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->dir != UP){ps->dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT){ps->dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT){ps->dir = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){ps->state = ESC; // 将状态该成 退出状态break;}else if (KEY_PRESS(VK_SPACE)) // 空格 :(VK_SPACE){//游戏要暂定// 封装一个函数 Pause() : 暂定和回复暂定,总不能一直暂停下去呀Pause();//暂定和回复暂定}else if (KEY_PRESS(VK_F3)) // 加速即休眠时间 变短,同时分数变多,而且 次数有限制{if (ps->SleepTime >= 80) // 最低下限:SleepTime = 80 (初始化sleep 是 200ms){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)) // 减速 即休眠时间 变长,同时分数变少{if (ps->FoodWeight > 2) // 最低下限:FoodWeight > 2{ps->SleepTime += 30;ps->FoodWeight -= 2;}}// 三 四 步 别写反// 三、走一步: 每轮移动一步,要检测是否撞墙.....等状态// 蛇身移动较为复杂,封装成一个函数便于分析SnakeMove(ps);// 四、睡眠一下Sleep(ps->SleepTime);} while (ps->state == OK); // 结束条件:当游戏状态 不是 OK 时 就结束}void GameEnd(pSnake ps)
{SetPos(15, 12);switch (ps->state){case ESC:printf("主动退出游戏\n");break;case KILL_BY_WALL:printf("很遗憾,撞墙了,游戏结束\n");break;case KILL_BY_SELF:printf("很遗憾,撞到自己,游戏结束\n");break;}//释放pSnakeNode cur = ps->pSnake;pSnakeNode del = NULL;while (cur){del = cur;cur = cur->next;free(del);}free(ps->pFood);ps = NULL;}

Snake.h

#pragma once
#include<locale.h>
#include<stdlib.h>
#include<stdio.h>
#include<windows.h>
#include<stdbool.h>
#define WALL L'□' // 因为 每次打印都要写一次 L什么什么的  还不如直接定义一个宏
#define BODY L'●' // 打印是 蛇身
#define FOOD L'※' // 打印食物
//Đʘ◨ↇↀↈ:不行的
// D●
// 蛇 默认的起始坐标位置:要确定 起始的蛇身 每个 节点的 坐标#define POS_X 24//蛇初始坐标
#define POS_Y 5#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
// 枚举类型不熟的可以去翻笔记
// enum  就是 集合版的 define//蛇走的方向
enum DIRECTION
{UP = 1, // 上DOWN, // 下LEFT, // 左RIGHT // 右
};//蛇的状态:  维护游戏状态的 枚举类型
enum GAME_STATE
{OK,//正常运行ESC, // 按 ESC 键退出KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到自己//END_NOMAL//正常结束
};// 蛇身节点
// 蛇身用链表来维护:一个蛇身节点就是一个链表节点
// 蛇在行动时,蛇身的每一个节点的坐标都在变化,所以要不断记录下来
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
// 创建一个指向蛇身节点的结构体指针//贪吃蛇
// 整条蛇
// 要管理整条贪吃蛇当前状态过程的信息,我们再封装一个Snake的结构来维护整条贪吃蛇:
typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针:指向蛇头pSnakeNode pFood;//维护食物的指针:食物实际上也是蛇身节点,只是打印不一样enum DIRECTION dir;//蛇头的方向默认是向右enum GAME_STATUS state;//维护游戏进行状态:蛇身撞墙、吃到自己、手动退出(上面定义了一个枚举类型)int Score;//当前获得分数int FoodWeight;//默认每个食物 10 分int SleepTime;//每走一步休眠时间:控制蛇移动的速度(本质:蛇身在移动过程中 可以发现,蛇身节点在 一闪一闪的停顿,其实是Sleep 控制睡眠时间,来控制总体移动速度)
}Snake, * pSnake;  // pSnake 是 指向贪吃蛇的指针: 下面就用上了,其实就是 Snake*//定位控制台光标位置
void SetPos(int x, int y);//游戏开始前的准备
void GameStart(pSnake ps);//欢迎界面
void WelcomeToGame();//打印欢迎信息//绘制地图
void CreateMap();//创建地图//初始化蛇
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//游戏运行的整个逻辑
void GameRun(pSnake ps);//打印帮助信息
void PrintHelpInfo();//蛇移动的函数- 每次走一步
void SnakeMove(pSnake ps);//判断蛇头的下一步要走的位置处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext);//下一步要走的位置处就是食物,就吃掉食物
void EatFood(pSnake ps, pSnakeNode pNext);//下一步要走的位置处不是食物,不吃食物
void NotEatFood(pSnake ps, pSnakeNode pNext);//检测撞墙
void KillByWall(pSnake ps);//撞到自己
void KillBySelf(pSnake ps);//游戏结束资源释放
void GameEnd(pSnake ps);

 test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"void test()
{int ch = 0;do{// 创建一条贪吃蛇Snake snake = { 0 };// 把蛇传过去:因为蛇移动时,就是变化的过程,则应该传地址GameStart(&snake); // 游戏开始前的初始化:按键功能提示、游戏界面打印、墙体初始化等等GameRun(&snake); // 游戏过程GameEnd(&snake);//善后工作SetPos(20, 15);printf("再来一局?(y/n)");ch = getchar();getchar();} while (ch == 'Y' || ch == 'y');}
int main()
{//适配本地中文环境setlocale(LC_ALL, "");//贪吃蛇游戏的设置test();return 0;
}

相关文章:

【C语言】贪吃蛇 详解

该项目需要的技术要点 C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等。 由于篇幅限制 和 使知识模块化&#xff0c; 若想了解 使用到的 Win32API 的知识&#xff1a;请点击跳转&#xff1a;【Win32API】贪吃蛇会使用到的 Win32API 目录 1. 贪吃蛇游…...

Mysql MGR搭建

一、架构说明 1.1 架构概述 MGR(单主)VIP架构是一种分布式数据库架构&#xff0c;其中数据库系统采用单主复制模式&#xff0c; 同时引入虚拟IP(VIP)来提高可用性和可扩展性。 这种架构结合了传统主从复制和虚拟IP技术的优势&#xff0c;为数据库系统提供了高可用、 高性能和…...

新火种AI|寒武纪跌落神坛!七年连亏50亿,AI芯片第一股不行了吗?

作者&#xff1a;文子 编辑&#xff1a;小迪 连年亏损&#xff0c;烧钱不止&#xff0c;寒武纪终是走到悬崖边缘。 寒武纪市值腰斩&#xff0c;连续七年累亏50亿 继连续六年亏损之后&#xff0c;寒武纪又迎来第七年亏损。 1月30日晚&#xff0c;寒武纪正式对外发布2023年年…...

three.js CSS3DObject、CSS2DObject、CSS3DSprite、Sprite的作为标签的区别

CSS3DObject、CSS2DObject、CSS3DSprite、Sprite的作为标签的区别 是否面向相机场景缩放时&#xff0c;是否会跟随是否会被模型遮挡CSS2DObject是否否CSS3DObject否是否CSS3DSprite是是是Sprite是是是 CSS3DObject 和 CSS3DRenderer 搭配来渲染标签&#xff1b; CSS2DObject …...

第7节、双电机直线运动【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;前面章节主要介绍单个电机控制&#xff0c;本节内容介绍两个电机完成Bresenham直线运动 一、Bresenham直线算法介绍 Bresenham直线算法由Jack Elton Bresenham于1962年在IBM开发&#xff0c;最初用于计…...

【C语言 - 哈希表 - 力扣 - 相交链表】

相交链表题目描述 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0…...

C++参悟:内存管理-unique_ptr

内存管理-unique_ptr 一、概述二、成员函数1. 构造、析构函数函数1. 构造函数2. 析构函数3. 赋值号 2. 修改器1. release&#xff08;&#xff09;2. reset()3. swap() 3. 观察器1. get()2. get_deleter3. bool 运算 一、概述 std::unique_ptr 是通过指针占有并管理另一对象&a…...

【征稿已开启】第五大数据、人工智能与软件工程国际研讨会(ICBASE 2024)

第五大数据、人工智能与软件工程国际研讨会&#xff08;ICBASE 2024&#xff09; 2024 5th International Conference on Big Data & Artificial Intelligence & Software Engineering 2024年09月20-22日 | 中国温州 第五届大数据、人工智能与软件工程国际研讨会&…...

Vue3父子组件传参

一&#xff0c;父子组件传参&#xff1a; 应用场景&#xff1a;父子组件传参 Vue3碎片&#xff1a;defineEmits&#xff0c;defineProps&#xff0c;ref&#xff0c;reactive&#xff0c;onMounted 1.父组件传子组件 a.父组件传参子组件 import { ref} from vue import OnChi…...

SpringBoot整理-微服务

Spring Boot 在构建微服务架构的应用中发挥着关键作用。微服务是一种将大型复杂应用拆分为更小、更容易管理和维护的服务的架构风格。每个服务通常围绕特定的业务功能构建,并且可以独立部署、扩展和更新。Spring Boot 提供了一系列特性和工具,使得创建和维护这些独立服务变得…...

服务器和CDN推荐

简介 陆云Roovps是一家成立于2021年的主机服务商&#xff0c;主要业务是销售美国服务器、香港服务器及国外湖北十堰高防服务器&#xff0c;还有相关CDN产品。&#xff08; 地址&#xff1a;roovps&#xff09; 一、相关产品...

c#读取csv文件中的某一列的数据

chat8 (chat779.com) 上面试GPT-3.5,很好的浏览网站&#xff0c;输入问题&#xff0c;可得到答案。 问题1&#xff1a;c#如何在csv中读取某一列数据 解答方案&#xff1a;在 C#中&#xff0c;你可以使用File.ReadAllLines来读取CSV中的所有行&#xff0c;然后逐行解析每一行…...

不懂快团团大团长对接?凭什么快团团的钱轮到你赚?

对接头部快团团大团长&#xff0c;让快团团大团长帮你卖货 分享几个推品的关键词&#xff1a; 1.推品的内容&#xff1a;产品实拍图核心卖点 不要上来就发笔记&#xff0c;你的产品图和文案还没吸引人&#xff0c;就发笔记没有人看。 可以先发你产品的简短卖点和图片&#xff…...

OpenGL 入门(九)—Material(材质)和 光照贴图

文章目录 材质设置材质光的属性脚本实现 光照贴图漫反射贴图高光反射贴图 材质 材质本质是一个数据集&#xff0c;主要功能就是给渲染器提供数据和光照算法。 如果我们想要在OpenGL中模拟多种类型的物体&#xff0c;我们必须针对每种表面定义不同的材质(Material)属性。 我们…...

jmeter-03界面介绍

文章目录 主界面介绍工具栏介绍测试计划介绍线程组介绍线程组——选择测试计划&#xff0c;右键-->添加-->线程-->线程组1.线程数2.准备时长(Ramp-up)3.循环次数4.same user on each iteratio5.调度器 主界面介绍 工具栏介绍 新建测试计划&#xff1a;创建一个空白的测…...

探究 MySQL 中使用 where 1=1 是否存在性能影响

文章目录 前言聊聊 mybatis 中多条件拼接的两种常规写法where 11使用 <where> 标签 性能影响where 11<where> 标签 总结个人简介 前言 最近在项目中使用 mybatis 写 SQL 使用了 where 11 来简化多条件拼接的写法&#xff0c;案例如下&#xff0c;借此聊聊多条件拼…...

VSCode无法启动:Waiting for server log...

问题基本情况 [13:30:20.720] > code 1.86.0 (commit 05047486b6df5eb8d44b2ecd70ea3bdf775fd937) [13:30:20.724] > Running ssh connection command... /var/fpwork/reiss/vscdata/server/cplane/.vscode-server/code-05047486b6df5eb8d44b2ecd70ea3bdf775fd937 comman…...

VMware虚拟机清理瘦身

用了一段时间VMware虚拟机之后&#xff0c;发现内存越来越小&#xff0c;也没装什么软件。。。 1.查询磁盘空间分布 虚拟机中磁盘空间查询 先看一下哪些地方占用的空间大&#xff0c;进行排查。 2.排查VMware复制文件产生的缓存路径 VMware复制文件有一个特点&#xff0c;以…...

Coil:Android上基于Kotlin协程的超级图片加载库

Coil&#xff1a;Android上基于Kotlin协程的超级图片加载库 1. coil简介 在当今移动应用程序的世界中&#xff0c;图片加载是一个不可或缺的功能。为了让应用程序能够高效地加载和显示图片&#xff0c;开发人员需要依赖于强大的图片加载库。而今天&#xff0c;我将向大家介绍…...

时间序列(Time-Series)MultiWaveletCorrelation.py代码解析

#这两行导入了PyTorch和NumPy库&#xff0c;分别用于深度学习和数值计算 import torch import numpy as np #这两行导入了PyTorch的神经网络模块和函数模块。 import torch.nn as nn import torch.nn.functional as F from torch import Tensor from typing import List, Tuple…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

Qemu arm操作系统开发环境

使用qemu虚拟arm硬件比较合适。 步骤如下&#xff1a; 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载&#xff0c;下载地址&#xff1a;https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

c# 局部函数 定义、功能与示例

C# 局部函数&#xff1a;定义、功能与示例 1. 定义与功能 局部函数&#xff08;Local Function&#xff09;是嵌套在另一个方法内部的私有方法&#xff0c;仅在包含它的方法内可见。 • 作用&#xff1a;封装仅用于当前方法的逻辑&#xff0c;避免污染类作用域&#xff0c;提升…...

写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

写一个shell脚本&#xff0c;把局域网内&#xff0c;把能ping通的IP和不能ping通的IP分类&#xff0c;并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...

未授权访问事件频发,我们应当如何应对?

在当下&#xff0c;数据已成为企业和组织的核心资产&#xff0c;是推动业务发展、决策制定以及创新的关键驱动力。然而&#xff0c;未授权访问这一隐匿的安全威胁&#xff0c;正如同高悬的达摩克利斯之剑&#xff0c;时刻威胁着数据的安全&#xff0c;一旦触发&#xff0c;便可…...

npm安装electron下载太慢,导致报错

npm安装electron下载太慢&#xff0c;导致报错 背景 想学习electron框架做个桌面应用&#xff0c;卡在了安装依赖&#xff08;无语了&#xff09;。。。一开始以为node版本或者npm版本太低问题&#xff0c;调整版本后还是报错。偶尔执行install命令后&#xff0c;可以开始下载…...