C语言·贪吃蛇游戏(下)

        上节我们将要完成贪吃蛇游戏所需的前置知识都学完了,那么这节我们就开始动手写代码了

1. 程序规划

        首先我们应该规划好我们的代码文件,设置3个文件:snack.h 用来声明游戏中实现各种功能的函数,snack.c 用来实现函数,test.c 用来测试和运行这个游戏

                        

        然后我们将游戏的大概流程在test.c文件中写出来,因为我们会用到宽字符,所以要记得适配本地中文环境

                

2. 游戏数据结构设计

2.1 蛇身数据结构(链表)

        我们设计用链表来表示蛇身,链表的每个节点中需要存这节蛇身的坐标 x、y 和下一节点的地址,然后我们给蛇身节点结构体类型改个好写一点的名字

        链表的基础知识:数据结构·单链表-CSDN博客

                

2.2 维护整个游戏的结构体

        我们还要创建一个结构体,用来存放当前游戏的种种状态

        

        

        

3. 游戏开始前的初始化

        游戏的开始前初始化都在GameStart()函数中实现,那么我们着手开始写这个函数。

        首先,我们要先创建维护贪吃蛇游戏的结构体,将它的信息传到GameStart()中去,在snack.h中声明这函数,然后再在snack.c中实现它

        

        我们把这个函数种封装的功能先都展示出来,之后就不重复展示了

3.1 控制台信息设置及光标隐藏

        控制台为了美观我们设置成100列30行的,名字就叫做贪吃蛇

        运行以下发现控制台设置好了,并且光标也很好的隐藏了起来

3.2 打印欢迎信息

        WelcomeToGame()函数内部的大部分东西我们上节其实已经写过了,下面就展示一下

3.3 打印地图

        地图大小我们上节已经说过了,要设计一个27行58列的棋盘,然后我们用一个宏把每块墙写出来,方便写打印语句

                        

        在打印墙的时候上边界和下边界挨着打印就行,左边界和右边界的时候要在每次打印之前把光标都定一下位

        展示打印地图的代码:

                

3.4 初始化蛇

        蛇身我们采用链表的结构进行管理。最开始,我们展示5个蛇身,初始的蛇尾巴从(24,5)的位置开始,向右5节之后是蛇头,这里我们采用头插的方式依次创建节点并加入蛇身中。

        同时定义蛇尾初始位置的宏和蛇身形状的宏

                        ​​​​​​​        

        最后将贪吃蛇的所有状态进行初始化

3.5 创建食物

        创建食物时不能随意创建的,要满足一下四点要求:

        1. 食物是随机出现的,就是说坐标是随机的

        2. 坐标必须在墙内    (x:2~54  y:1~25)

        3. 坐标不能在蛇身上

        4. 横坐标必须是偶数

        创建的食物其实就相当于蛇的一段身体,所以我们之间用蛇身节点的结构体类型保存食物的()(位置)信息就行,然后食物的创建要满足上面的要求,最后要注意的是,我们想要生成真随机数,就不要忘了在程序开始时给rand()函数种种子srand()

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

4. 游戏的运行过程

        下面我们来完成GameRun()函数,首先将这个函数中要实现的功能理出来

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

4.1 打印帮助信息

        这个没啥好说的,定好位打印就行

        ​​​​​​​        

4.2 按键逻辑

        写一个do···while循环,当游戏状态是OK的情况下就一直循环,持续检测按键的状态,同时在这里把帮助信息也写一下。我们检测按键的时候写一个宏 KEY_PRESS  内容跟上节的一样,这里我就不展示了,最后我会把所有代码都贴出来的

        ​​​​​​​        

        pause()函数

                        

        

4.3 蛇的移动(走一步)

        这里我们的策略是在蛇头前面再创建一个节点,用来判断蛇要走的下一个位置是否有食物,这里要注意,当蛇向下走的时候不能直接向上走,向右走的时候不能像左走,以此类推。蛇的下一步的位置信息先全都存在新创建的节点中,等待接入蛇身

        

        ​​​​​​​        

4.3.1 蛇头下一步是食物

        ​​​​​​​            

4.3.2 蛇头下一步不是食物

        ​​​​​​​   ​​​​​​​        ​​​​​​​

4.3.3 检测撞墙

        撞墙简单,判断一下蛇头横纵坐标有没有跟墙重合就行

                

4.3.4 检测撞自己

        同理,蛇头的横纵坐标没有跟自身重合就行

5. 游戏结束善后的工作

        善后工作其实就是打印一下蛇的死亡原因,然后把,malloc的内存都释放掉

             ​​​​​​​

6. 复玩功能

        复玩功能我们之前已经写过很多次了,像扫雷中就有很详细的介绍,这里我就简单写一下了

        扫雷请参考:函数·扫雷游戏-CSDN博客

        ​​​​​​​        ​​​​​​​

7. 完整代码

        snack.h

#include<locale.h>
#include<stdlib.h>
#include<stdio.h>
#include<Windows.h>
#include<stdbool.h>
#include<time.h>#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'//蛇默认的起始坐标
#define POS_X 24
#define POS_Y 5#define KEY_PRESS(vk) ((GetAsyncKeyState(vk) & 0x1) ? 1 : 0)//游戏当前状态
enum GAME_STATUS
{OK = 1,//游戏正常运行ESC,//玩家按esc要退出KILL_BY_WALL,//蛇撞墙死KILL_BY_SELF //蛇撞自己死
};//当前蛇在向哪个方向移动
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//蛇身节点
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//贪吃蛇
typedef struct Snake
{pSnakeNode pSnake;//蛇头位置pSnakeNode pFood;//指向食物的指针int Score;//当前累计的分数int FoodWeight;//一个食物的分数//蛇休眠的时间//休眠时间越短,蛇移动的越块int SleepTime;enum GAME_STATUS status;//游戏当前的状态enum DIRECTION dir;//蛇当前走的方向//···
}Snake, * pSnake;//修改光标位置
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);

        snack.c

#include"snack.h"//修改光标位置
void SetPos(int x, int y)
{//获得设备句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//根据句柄设置光标位置COORD pos = { x,y };SetConsoleCursorPosition(handle, pos);
}
//打印欢迎信息
void WelcomeToGame()
{//欢迎界面SetPos(38, 12);printf("欢迎来到贪吃蛇小游戏\n");SetPos(40, 20);system("pause");system("cls");//清空屏幕//操作介绍界面SetPos(20, 10);printf("用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");SetPos(37, 12);printf("加速能得到更高的分数\n");SetPos(40, 20);system("pause");system("cls");//清空屏幕}//打印地图
void CreateMap()
{int i = 0;//上边界SetPos(0, 0);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//下边界SetPos(0, 26);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左边界for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右边界for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}//初始化蛇
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;int i = 0;for (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){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = 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;ps->status = OK;
}//创建食物
void CreateFood(pSnake ps)
{int x = 0, y = 0;again:do{//当x不是偶数就再生成一次x = rand() % 53 + 2;y = rand() % 24 + 1;} while (x % 2 != 0);//坐标和蛇的身体的每个节点坐标比较pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//创建食物pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood():malloc()");return;}//食物其实就相当于一段蛇身,吃了之后把它接上去就行pFood->x = x;pFood->y = y;ps->pFood = pFood;SetPos(x, y);wprintf(L"%lc", FOOD);
}//游戏开始前的初始化
void GameStart(pSnake ps)
{//控制台信息设置system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;SetConsoleCursorInfo(handle, &CursorInfo);//打印欢迎信息WelcomeToGame();//打印地图CreateMap();//初始化蛇InitSnake(ps);//创建食物CreateFood(ps);
}void PrintHelpInfo()
{SetPos(62,15);printf("1. 不能撞墙,不能咬到自己");SetPos(62, 16);printf("2. 用 ↑ ↓ ← → 控制蛇的移动"); SetPos(62, 17);printf("3. F3为加速,F4为减速");}void pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}//蛇头下一步要走的坐标是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext)
{if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y){//下一个坐标是食物return 1;}else{//不是食物return 0;}
}//是食物就吃掉
void EatFood(pSnake ps, pSnakeNode pNext)
{//是食物就把新节点头插pNext->next = ps->pSnake;ps->pSnake = pNext;//打印整条蛇pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", 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 pcur = ps->pSnake;while (pcur->next->next){//这里捎带着把新蛇身打印SetPos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->next;}//先将尾节点的位置打印空白字符SetPos(pcur->next->x, pcur->next->y);printf("  ");//打印两个空格//最后freefree(pcur->next);pcur->next = NULL;
}//检测撞墙
void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 || \ps->pSnake->x == 56 || \ps->pSnake->y == 0 || \ps->pSnake->y == 26){ps->status = 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->status = KILL_BY_SELF;return;}cur = cur->next;}
}//蛇的移动
void SnakeMove(pSnake ps)
{//在蛇头前创建一个检测下一个节点是什么的节点pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove():malloc()");return;}pNext->next = NULL;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)){//是食物就吃掉EatFood(ps, pNext);}else{//不是食物就走一步NotEatFood(ps, pNext);}//检测撞墙KillByWall(ps);//检测撞自己KillBySelf(ps);
}//游戏的运行过程
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//当前的分数情况SetPos(62, 10);printf("总分:%d", ps->Score);SetPos(62, 11);printf("食物的分值:%02d", ps->FoodWeight);//检测按键//上、下、左、右、ESC、空格、F3、F4if (KEY_PRESS(VK_UP) && ps->dir != 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->status = ESC;break;}else if (KEY_PRESS(VK_SPACE)){//暂停游戏pause();}else if (KEY_PRESS(VK_F3)){//睡眠时间不能无限减小,控制一下if (ps->SleepTime >= 80){//加速,休眠时间变短ps->SleepTime -= 30;//每个食物得分增加ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){//睡眠时间不能无限减小,控制一下if (ps->FoodWeight > 2){//减速,休眠时间变长ps->SleepTime += 30;//每个食物得分减少ps->FoodWeight -= 2;}}//睡眠一下Sleep(ps->SleepTime);//蛇的移动SnakeMove(ps);} while (ps->status == OK);
}//游戏结束善后的善后工作
void GameEnd(pSnake ps)
{SetPos(20, 11);switch (ps->status){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 = ps->pSnake;while (cur){del = cur;cur = cur->next;free(del);}ps->pSnake = NULL;//释放食物节点free(ps->pFood);ps->pFood = NULL;}

        test.c

#include"snack.h"void test()
{int ch;//getchar()返回的是整形ASCII码值do{//创建贪吃蛇游戏Snake snake = { 0 };GameStart(&snake);//游戏开始前的初始化GameRun(&snake);//游戏的运行过程GameEnd(&snake);//游戏结束善后的工作SetPos(25, 15);printf("再来一局吗?(Y/N):>");ch = getchar();getchar();//清理缓冲区的\n} while (ch == 'Y');
}int main()
{srand((unsigned int)time(NULL));//修改适配本地中文环境setlocale(LC_ALL, "");//贪吃蛇游戏的测试test();//控制程序退出结语的位置SetPos(0, 27);return 0;
}

8. 结语

        那么到此,贪吃蛇游戏就写好了,当然,这个游戏的功能还有美观度还有待各位共同开发,如果说想要给这个游戏图形化一下,大家可以去 easyX 看看EasyX Graphics Library for C++,把这个库下载下来应该就能在VS上用了

        同时,我们的C语言讲解就告一段落了,下一阶段我将着手数据结构的相关知识

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/662844.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索Web3.0:下一代互联网的新篇章

随着技术的不断演进和社会的持续发展&#xff0c;我们正逐渐迈入Web3.0时代。Web3.0&#xff0c;作为下一代互联网的代名词&#xff0c;不仅仅是技术的进步&#xff0c;更是一种全新的数字化生态系统&#xff0c;其所带来的影响将深刻地改变着我们的生活、工作和交流方式。 什…

Java二维码图片识别

前言 后端识别二维码图片 代码 引入依赖 <dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.2.1</version></dependency><dependency><groupId>com.google.zxing<…

tuya-open-sdk-for-device使用体验之Windows 下 MSYS2 编译 T2-U 开发板

tuya-open-sdk-for-device 是一款跨芯片平台、操作系统的 IoT 开发框架。它基于通用南向接口设计&#xff0c;支持 Bluetooth、Wi-Fi、Ethernet 等通信协议&#xff0c;提供了物联网开发的核心功能&#xff0c;包括配网&#xff0c;激活&#xff0c;控制&#xff0c;升级等&…

2024美赛A题完整思路代码分析:建立竞争机理方程+遗传算法优化

A题是自由度比较大的场景限定下的模型构建&#xff0c;相对比较容易&#xff0c;核心是找到现有的成熟的数学模型&#xff0c;然后找到合适的数据进行证明得到结论&#xff0c;估计大部分是目标优化问题。&#xff08;不限制专业&#xff09; B题属于较为经典的物理建模&#…

【竞技宝】LOL:Able小炮连续起跳收割战场 OMG2-0轻取TT

北京时间2024年2月2日&#xff0c;英雄联盟LPL2024春季赛在昨天迎来第二周第四个比赛日&#xff0c;本日首场比赛由TT对阵OMG。本场比赛&#xff0c;TT在前中期和OMG有来有回&#xff0c;然而中后期的大龙团战始终不是OMG的对手&#xff0c;最终OMG2-0轻取TT。以下是本场比赛的…

linux vim 异常退出 异常处理 交换文件

交换文件 *.swp 格式 同时是隐藏的 如在vim一个文件&#xff0c; 在没有正常退出&#xff0c; 如直接断开连接 在次编辑这个文件 会出现下图的错误 解决方案&#xff1a; 直接删除这个交换文件即可 rm -fr .zen.txt.swp

唐墓惊现石椁,文物预防性保护系统未雨绸缪

一、文物保护的急需解决和科技的支持 陕西省考古学会近日宣布&#xff0c;考古团队在西安揭开了唐睿宗李旦孙媳妇薛柔顺墓的神秘面纱&#xff0c;其中出土的一具完整石椁&#xff0c;雕刻精湛、线条流畅&#xff0c;实属罕见珍宝。唐代石椁本就稀少&#xff0c;此次发现更是为…

RK3588开发板Ubuntu与开发板使用U盘互传

1 将 U 盘(U 盘的格式必须为 FAT32 格式&#xff0c;大小在 32G 以下)插到开发板的 usb 接口&#xff0c;串口打印信息如下所示&#xff0c;U 盘的设备节点是/dev/sdb4。U 盘的设备节点不是固定的&#xff0c;根据实际情况来查看设备节点。 2 输入以下命令挂载 U 盘&#xff0c…

Leetcode—2670. 找出不同元素数目差数组【简单】

2024每日刷题&#xff08;一零七&#xff09; Leetcode—2670. 找出不同元素数目差数组 哈希表实现代码 class Solution { public:vector<int> distinctDifferenceArray(vector<int>& nums) {unordered_set<int> s;int n nums.size();vector<int&g…

Qwen-VL 技术报告总结

感谢如此优秀的开源工作,仓库链接 Qwen-VL 权重分为 Qwen-VL && Qwen-VL-Chat,区别文档稍后介绍 训练过程 在第一阶段中主要使用224X224分辨率训练,训练数据主要来源是公开数据集,经过清洗,数据总量大约是1.4B,中文数据和英文j训练目标是视觉语言和文本语言对齐。…

docker集成 nacos/nacos-server (包括踩的坑)

tips 这边需要的数据库我已经安装好了&#xff0c;所以数据库的安装这边已经省略了 拉取镜像&#xff08;这边使用nacos1.4.1作为例子&#xff09; docker pull nacos/nacos-server:1.4.1创建映射的文件夹 (conf存放配置文件&#xff0c;logs存放日志文件) mkdir -p /data/n…

MySQL索引的原理和SQL优化策略

1. 索引 在InnoDB存储引擎中&#xff0c;索引分为聚簇索引和辅助索引两种类型。 聚簇索引是指基于表的主键构建的索引&#xff0c;它决定了表中数据的物理存储顺序。也就是说&#xff0c;聚簇索引中的键值按照主键的顺序来排序&#xff0c;并且每个叶子节点存储的是整个表行的…

HAL库配置PWM模式

一、什么是PWM 脉冲宽度调制(PWM)&#xff0c;是英文“Pulse Width Modulation”的缩写&#xff0c;简称脉宽调制。通过控制高低电平在一个周期内的占比从而输出一定的电压。 向上计数原理介绍 ​PWM的一个周期 定时器从0开始向上计数 当0-t1段,定时器计数器TIMx_CNT值小于…

直播团队职责

一、内容策划 直播团队的内容策划人员是整个直播活动的核心&#xff0c;他们需要负责策划直播的主题、内容、形式以及时间安排等。同时&#xff0c;他们还需要负责邀请嘉宾、安排活动等&#xff0c;确保直播内容丰富、有趣、有价值。 二、主播管理 主播是直播活动的关键人物…

unity WebGL发布游戏生成WebGL

1.unty Hub中安装WEBGL支持 2.项目平台的切换 color space需要根据项目选择 ColorSpace&#xff0c;是指玩家设置的颜色空间。 伽马颜色空间是历史悠久的标准格式&#xff0c;但线性颜色空间渲染可提供更精确的结果。 具体区别&#xff1a;ColorSpace 3.由于没有自己服务器…

壹[1],Xamarin开发环境配置

1&#xff0c;环境 VS2022 注&#xff1a; 1&#xff0c;本来计划使用AndroidStudio&#xff0c;但是也是一堆莫名的配置让人搞得很神伤&#xff0c;还是回归C#。 2&#xff0c;MAUI操作类似&#xff0c;但是很多错误解来解去&#xff0c;且调试起来很卡。 3&#xff0c;最…

如果你也觉得自己不够聪明,也缺乏才华。。。

​在追求成功的道路上&#xff0c;我们常常自我怀疑&#xff0c;感觉自己不够聪明&#xff0c;缺乏必要的才华。然而&#xff0c;正是这种自我感知&#xff0c;如果处理得当&#xff0c;可以成为我们最大的优势。这篇文章旨在为那些怀疑自己的能力&#xff0c;但依然渴望在工作…

图片热区功能

一、需求描述及效果图 1.需求描述&#xff1a; 根据后端返回的坐标及人员信息&#xff0c;在图片上的相应位置添加图片热区功能&#xff0c;点击可展示出对应的人员信息。 图片可进行缩放 2.示例&#xff1a; &#xff08;定位是随便写的&#xff0c;仅做示例&#xff09; …

Mac用Crossover玩《幻兽帕鲁》手柄不能用怎么办? Mac电脑玩《幻兽帕鲁》怎么连接手柄? 幻兽帕鲁玩家超1900万

2024年首款爆火Steam平台的游戏《幻兽帕鲁》&#xff0c;在使用Crossover后可以用Mac系统玩了&#xff0c;很多玩家喜欢通过手柄玩游戏&#xff0c;它拥有很好的握持体验&#xff0c;长时间玩也不会很累&#xff0c;所以很多《幻兽帕鲁》玩家都喜欢用手柄来操作&#xff0c;很多…

史上最全知识图谱建模实践(下):多元关系架构

在“知识图谱之本体结构与语义解耦——基于OpenSPG的建模实践&#xff08;上&#xff09;”一文中&#xff0c;我们从实体关系设计和概念语义建模2种场景&#xff0c;讲解了基于SPG的知识建模的方法和案例。 本文中&#xff0c;我们将继续讲解多元关系架构场景中的知识建模实践…