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

发布时间:2026/10/25 13:17:40
原神抽卡模拟器C++源码解析:概率模型与保底机制实现

简介:这是一份面向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.md

src放源码,data放卡池配置,Makefile是 Linux/macOS 下的构建脚本。Windows 用户如果没装 make,直接手动编译也行。先确认你的工具链:g++ --version能输出版本号就说明编译器就位。没有的话,Windows 装 MinGW-w64,macOS 装 Xcode Command Line Tools,Linux 一条sudo apt install build-essential搞定。

2.2 编译命令与参数含义

最省事的编译方式,在项目根目录执行:

g++ -std=c++17 -O2 -o gacha src/main.cpp src/gacha.cpp

逐段拆开看:-std=c++17指定语言标准,这个工程用到了<random>里的std::mt19937和std::uniform_real_distribution,C++11 以上都支持,但作者写的是 17;-O2开二级优化,批量模拟时能快不少;-o gacha指定输出文件名,Windows 下会生成gacha.exe。如果报“undefined reference”之类的链接错误,八成是漏了src/gacha.cpp,两个源文件必须一起编。

编译通过后直接运行:

./gacha

Windows 下是gacha.exe。第一次跑你会看到一个交互菜单,通常是让你选卡池类型、输入模拟次数、是否开启详细日志。别急着敲数字,先看下一节怎么配参数。

2.3 卡池配置文件怎么读怎么写

data/pool_config.txt是整个模拟器的灵魂。常见格式是键值对,类似:

base_rate=0.006 soft_pity_start=74 hard_pity=90 rate_up_prob=0.5 guarantee_after_loss=true

base_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, bins=range(1, 91), edgecolor='black') 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 自带的编译器版本太老,默认标准低于 C++11。解决:在编译选项里加-std=c++11或更高,或者直接换 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 万次模拟,看看概率再决定。从那以后我每次改概率参数都强制走一遍批量验证,再也没被“感觉快出了”骗过。希望这个模拟器也能帮你把抽卡这件事从玄学变成数学。

本文还有配套的精品资源,点击获取