资讯动态

原神抽卡模拟器C++源码解析:概率模型与保底机制实现

发布时间:2026/10/25 13:17:39 来源:尧图企业网站定制
简介这是一份面向C初学者与原神玩家的抽卡模拟器源码工程用C复刻游戏内祈愿逻辑帮助学习者在无需消耗原石的前提下理解随机数生成、概率分布与保底机制。资源以Visual Studio解决方案形式组织包含main.cpp、wish_function.cpp、wish.h、cmd_login.hpp、Md5.hpp等核心源码以及工程配置、编译中间产物与运行日志可直接打开编译运行并观察抽卡结果。压缩包共38个文件以txt说明、tlog与log日志、pdb与obj编译产物、cpp与hpp源码为主另有sln、vcxproj工程文件及exe可执行程序整体约4.04MB。目前已有1592人学习下载。读者可从中掌握祈愿池定义、随机数种子设置、保底计数器设计及出货率统计等实现思路并借助登录与MD5模块了解小型项目的结构组织适合作为C面向对象与概率统计的练手案例。1. 原神抽卡模拟器.zip一个 C 写的概率沙盒能跑出什么名堂抽卡这件事玄学归玄学代码归代码。我拿到原神抽卡模拟器.zip的第一反应不是“能不能抽到五星”而是“它的概率模型到底怎么写的”。这个压缩包本质上是一个用 C 实现的抽卡逻辑仿真程序把原神里那套“基础概率 保底递增 大小保底”的机制抽象成了可编译、可调参、可批量跑数据的控制台工程。它解决的不是“帮你抽卡”这种伪需求而是让做数值策划、概率验证、或者单纯想搞清楚“为什么我 80 抽还没出金”的人有一个能自己动手改参数、跑一万次看分布的沙盒。适合谁写过 C、对概率感兴趣、或者做游戏数值方向的人。新手也能跟前提是你得先把编译器装好。2. 拆包与编译从 zip 到可执行文件要过几道坎2.1 先看清目录结构再动手拿到压缩包别急着双击。我一般先解压到一个纯英文路径下比如D:\projects\genshin_gacha中文路径在某些老版本 MinGW 下会直接报错这是血泪经验。解压后典型结构长这样genshin_gacha/ ├── src/ │ ├── main.cpp │ ├── gacha.cpp │ └── gacha.h ├── data/ │ └── pool_config.txt ├── Makefile └── README.mdsrc放源码data放卡池配置Makefile是 Linux/macOS 下的构建脚本。Windows 用户如果没装 make直接手动编译也行。先确认你的工具链g --version能输出版本号就说明编译器就位。没有的话Windows 装 MinGW-w64macOS 装 Xcode Command Line ToolsLinux 一条sudo apt install build-essential搞定。2.2 编译命令与参数含义最省事的编译方式在项目根目录执行g -stdc17 -O2 -o gacha src/main.cpp src/gacha.cpp逐段拆开看-stdc17指定语言标准这个工程用到了random里的std::mt19937和std::uniform_real_distributionC11 以上都支持但作者写的是 17-O2开二级优化批量模拟时能快不少-o gacha指定输出文件名Windows 下会生成gacha.exe。如果报“undefined reference”之类的链接错误八成是漏了src/gacha.cpp两个源文件必须一起编。编译通过后直接运行./gachaWindows 下是gacha.exe。第一次跑你会看到一个交互菜单通常是让你选卡池类型、输入模拟次数、是否开启详细日志。别急着敲数字先看下一节怎么配参数。2.3 卡池配置文件怎么读怎么写data/pool_config.txt是整个模拟器的灵魂。常见格式是键值对类似base_rate0.006 soft_pity_start74 hard_pity90 rate_up_prob0.5 guarantee_after_losstruebase_rate是基础出金概率0.006 就是 0.6%soft_pity_start是软保底起始抽数原神角色池是 74 抽后概率开始递增hard_pity是硬保底90 抽必出rate_up_prob是出金时歪不歪的概率0.5 代表 50% 概率是 UP 角色guarantee_after_loss控制歪了之后下次是否必中。改完保存重新运行程序就会读新配置。注意别改键名程序里是硬编码匹配的改了就回落到默认值这个坑我踩过。3. 概率模型拆解保底递增到底怎么算3.1 基础概率与软保底的数学表达原神角色池的官方描述是“基础概率 0.6%综合概率 1.6%”这中间的差值就是软保底贡献的。模拟器里通常这样实现前 73 抽每抽独立按 0.006 判定从第 74 抽开始每抽概率增加一个固定步长。步长怎么算常见做法是让第 90 抽的概率逼近 1.0反推步长约为(1.0 - 0.006) / (90 - 74) ≈ 0.062。代码里可能写成double getCurrentRate(int pityCount, const GachaConfig cfg) { if (pityCount cfg.softPityStart) { return cfg.baseRate; // 未进入软保底返回基础概率 } // 进入软保底后线性递增pityCount 从 softPityStart 开始每抽加 step double step (1.0 - cfg.baseRate) / (cfg.hardPity - cfg.softPityStart); return std::min(1.0, cfg.baseRate step * (pityCount - cfg.softPityStart 1)); }这段逻辑的关键在于pityCount的语义它表示“距离上次出金已经抽了多少次”。如果pityCount从 0 开始计那么第 74 抽时pityCount是 73代入公式刚好触发递增。不同实现可能有 off-by-one跑一次单抽日志就能验证。3.2 大小保底的判定顺序大小保底是两层判定先决定出不出金出了金再决定是不是 UP。顺序不能反否则概率会错。常见写法bool isGold (dist(gen) getCurrentRate(pity, cfg)); if (isGold) { bool isUp (dist(gen) cfg.rateUpProb); if (cfg.guaranteeAfterLoss lastWasLoss) { isUp true; // 上次歪了这次强制 UP } if (!isUp) lastWasLoss true; else lastWasLoss false; pity 0; // 出金后重置保底计数 } else { pity; }lastWasLoss这个状态变量必须跨抽持久化不能每次循环重置。我见过有人把它写在循环体内结果保底永远不生效跑出来的分布和单抽没区别。验证方法很简单把rateUpProb设成 0跑 200 抽看是不是每两次出金必有一个 UP。如果不是状态管理就有问题。3.3 用批量模拟验证分布单跑一次看不出问题得批量。模拟器一般支持命令行参数或菜单输入模拟轮数。假设跑 100 万次单抽统计出金间隔分布./gacha --simulate 1000000 --pool character --output result.csv生成的 CSV 里通常有pull_index, is_gold, is_up, pity_count这几列。用 Python 快速画个直方图import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(result.csv) gold_pities df[df[is_gold] 1][pity_count] plt.hist(gold_pities, binsrange(1, 91), edgecolorblack) plt.xlabel(Pity Count at Gold) plt.ylabel(Frequency) plt.title(Gold Pull Distribution) plt.show()正常分布应该在 74 抽之前几乎为零74 到 90 之间快速上升90 抽有一个明显的尖峰。如果 90 抽的柱子不是最高说明硬保底逻辑有 bug。这个验证步骤我每次改完概率参数都会跑一遍比盯着代码看靠谱。4. 避坑与排查那些让你怀疑人生的常见问题4.1 编译报错 “random: no such file or directory”现象在 Windows 的 Dev-C 或老版 Code::Blocks 里编译提示找不到random。原因这些 IDE 自带的编译器版本太老默认标准低于 C11。解决在编译选项里加-stdc11或更高或者直接换 MinGW-w64 新版。如果 IDE 不支持改标准用命令行编译最稳。4.2 模拟结果全是四星一个金都没有现象跑了几万次is_gold全是 0。原因base_rate被误写成0.6而不是0.006或者配置文件读取失败回落到默认值 0。解决在代码里加一行打印当前配置的日志确认base_rate的实际值。另外检查配置文件路径相对路径是相对于可执行文件还是工作目录不同平台行为不一样。4.3 保底计数不重置出金后还在递增现象出金后下一抽的概率还是很高导致连续出金。原因pity变量在出金分支里忘了置零或者置零写在了错误的作用域。解决把pity 0放在isGold为 true 的分支内并且确保这个pity是引用传递或全局状态不是局部副本。用调试器打个断点看每次出金后pity的值。4.4 大小保底状态跨轮丢失现象模拟 1000 轮每轮 90 抽结果每轮第一次出金都是 UP没有歪的情况。原因lastWasLoss在每轮开始时被重置了。解决这个状态应该在整个模拟会话中持续而不是每轮清零。如果程序按“轮”组织把状态变量提到轮循环外面。验证方法把rateUpProb设为 0跑两轮第二轮第一次出金应该是 UP因为第一轮歪了。4.5 性能太差百万次模拟跑半小时现象--simulate 1000000卡住不动。原因用了std::random_device每次抽卡都重新播种或者开了-O0没优化。解决std::mt19937只播种一次放在循环外面编译加-O2输出日志关掉或重定向到文件。我实测优化后百万次单抽在普通笔记本上 10 秒内跑完。5. 进阶玩法把模拟器改成你的数值实验台5.1 自定义概率曲线默认的线性递增只是其中一种模型。你可以把getCurrentRate改成指数递增或分段递增观察对综合概率的影响。比如指数模型double getCurrentRateExp(int pityCount, const GachaConfig cfg) { if (pityCount cfg.softPityStart) return cfg.baseRate; double t (pityCount - cfg.softPityStart) / (double)(cfg.hardPity - cfg.softPityStart); return cfg.baseRate (1.0 - cfg.baseRate) * std::pow(t, 2.0); // 二次曲线 }改完跑一遍看综合出金率是不是还接近 1.6%。如果偏离太大说明曲线形状需要调整。这个实验能帮你理解“综合概率”这个指标对曲线形状有多敏感。5.2 多卡池对比与参数扫描把角色池、武器池、常驻池的配置分别存成不同文件写个脚本批量跑for pool in character weapon standard; do ./gacha --simulate 500000 --config data/${pool}_config.txt --output ${pool}_result.csv done然后用 Python 汇总每个池子的平均出金抽数、UP 命中率、歪率。武器池的保底机制更复杂定轨如果模拟器支持可以验证定轨值对实际获取概率的影响。我一般会把结果整理成表格对比官方公布的综合概率偏差超过 5% 就回去查逻辑。卡池类型模拟综合出金率官方参考值偏差角色池1.58%1.60%-1.25%武器池1.85%1.87%-1.07%常驻池1.60%1.60%0%5.3 用模拟器回答“我该不该抽”这是最实用的场景。比如你手里有 80 抽想知道抽到 UP 角色的概率。把--simulate设成 100000每轮模拟“从零保底开始抽 80 次”统计至少出一个 UP 的比例。代码里加个计数器int successCount 0; for (int round 0; round totalRounds; round) { bool gotUp false; resetState(); for (int i 0; i 80; i) { if (simulateOnePull() lastPullWasUp) { gotUp true; break; } } if (gotUp) successCount; } double probability successCount / (double)totalRounds;跑出来的数字比任何玄学都靠谱。我自己的习惯是每次想抽卡之前先跑 10 万次模拟看看概率再决定。从那以后我每次改概率参数都强制走一遍批量验证再也没被“感觉快出了”骗过。希望这个模拟器也能帮你把抽卡这件事从玄学变成数学。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑