This project cf-simulator is licensed under the GNU General Public License v3.0 (GPL-3.0).
Copyright (C) 2026 CHNragdoll
See the LICENSE file for details.
这是一套纯本地运行的抽奖活动模拟器。你可以把它当成一个“可改概率、可改奖池、可看统计、可做成本模拟”的活动沙盘。
如果你完全不懂代码,按本文的“第 1 步、第 2 步”做,也能跑起来并正常操作。
它支持:
项目根目录:/Users/apple/Documents/PyCharm/浩劫艾丽莎
关键目录和文件:
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/server.py- 后端主程序(HTTP 服务 + 抽奖逻辑 + 统计 + 模拟)
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/static/index.html- 前端页面结构
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/static/app.js- 前端交互逻辑(按钮事件、图表渲染、调用 API)
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/static/style.css- 前端样式
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/data/lottery.db- 奖池配置数据库(SQLite)
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/data/state.json- 你的模拟过程状态(消费、钥匙、抽奖历史等)
/Users/apple/Documents/PyCharm/浩劫艾丽莎/cf_images/- 活动图片资源(本地图片)
/Users/apple/Documents/PyCharm/浩劫艾丽莎/main.py- 批量下载图片脚本(需要
requests)
- 批量下载图片脚本(需要
在终端执行:
python3 --version看到 Python 3.x.x 就行。
cd "/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator"python3 server.py启动成功会看到类似:
Simulator running: http://127.0.0.1:18081
Admin running: http://127.0.0.1:18081/admin
DB path: .../lottery_simulator/data/lottery.db
浏览器访问:
http://127.0.0.1:18081
到这里就已经可以玩完整流程了。
数据库可视化后台地址:http://127.0.0.1:18081/admin
如果你不想手写 SQL,可以直接用图形化管理器:
cd "/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator"
python3 db_admin_gui.py特点:
- 直接编辑
lottery.db - 表名和字段名都有中文映射
- 支持新增、修改、删除
- 支持常见字段下拉(如 0/1、
item_type)
- 点击
10元1抽:消费 10 元,+1 钥匙 - 点击
100元11抽:消费 100 元,+11 钥匙
顶部会更新:
- 累计消费
- 剩余钥匙
- 总积分
- 总抽数
抽一次:消耗 1 把钥匙抽十次:消耗 10 把钥匙
结果逻辑:
- 抽中积分子项:直接加积分
- 抽中道具且
direct_to_warehouse=1:直接进仓库 - 抽中道具且
direct_to_warehouse=0:进暂存箱,等待你后续“发送”或“分解”
“暂存道具表”里每条记录可做两件事(一次性):
发送仓库分解
分解收益来自该道具配置:
- 积分分解活动:
decompose_points - 钥匙分解活动:
decompose_keys
展示所有“已进仓库”的道具数量(包括抽中直发 + 兑换发放 + 暂存发送)。
点击兑换卡片按钮(例如 1888积分兑换):
- 扣除对应积分
- 道具发往仓库
- 记录到兑换日志
如果该道具设置了限兑,会校验:
redeem_limit_enabled=1redeem_limit_count上限
点击 概率公布 可看所有奖项当前概率(积分组会展开成具体积分项)。
有两个 tab:
- 抽奖奖池记录(含“欧皇时刻”查看)
- 积分兑换记录
可查看:
- 命中分布
- 理论值 vs 实际值偏差
- 95% 置信区间 + Z 值
- 最近 50 抽 vs 全局
- 日趋势
- 卡方统计量
- 累计积分线性拟合(含公式和 R²)
你可以输入“模拟次数”,并选策略:
最低单钥匙成本优先10元1抽优先100元11抽
系统会异步跑任务并显示进度条,最后给出:
- 全分解均价 / P50 / P90 / 95% 区间
- 纯保底均价
- CDF 曲线
- 置信区间图
- 命中方式占比(抽中/兑换)
- 成本回归模型与训练/测试误差
路径:
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/data/lottery.db
主要表:
purchase_options:买钥匙方案points_groups:积分组(例如“积分”)prize_items:道具 + 积分子项 + 兑换项pool_layout_settings:奖池布局参数pool_palette_priority:稀有度(颜色)优先级popup_highlight_rules:哪些颜色触发“稀有弹窗”
路径:
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/data/state.json
常见字段:
money_spent:累计消费keys:当前钥匙points:当前积分total_draws:总抽数draw_counts:每个奖项命中次数stash:暂存箱聚合数量stash_records:暂存箱逐条记录(有 pending/sent/decomposed 状态)warehouse:仓库总量warehouse_draw:由抽奖直接进仓库的量redeem_logs:兑换日志history:抽奖历史(最多保留最近 300 条)
设奖池元素权重为 w_i,总权重:
W = Σ w_i
每个元素命中概率:
p_i = w_i / W
积分组本身是一个顶层元素,权重等于其子项权重和:
w_group = Σ w_child
积分子项全局概率:
p_child_global = w_child / W
积分子项组内条件概率:
p_child_in_group = w_child / w_group
后端使用“累计权重区间命中”方式:
- 计算总权重
total - 取随机数
r ~ U(0, total) - 按顺序累加权重
cur += w_i - 第一项满足
r <= cur即命中
这就是 weighted_pick 的逻辑。
抽到积分:
points += 子项积分值draw_counts[积分名称] += 1
抽到道具:
- 若
direct_to_warehouse=1:进入仓库 - 否则进入暂存箱,并写入
stash_records
同时:
total_draws += 1- 抽奖结果写入
history(最多保留 300 条)
若总抽数为 n,某奖项理论概率为 p:
- 理论命中数:
E = n * p - 实际命中数:
O - 偏差:
Δ = O - E
对观测命中率 p̂ = O / n,后端用 Wilson 区间(z=1.96):
中心:
center = (p̂ + z²/(2n)) / (1 + z²/n)
半径:
half = z * sqrt( p̂(1-p̂)/n + z²/(4n²) ) / (1 + z²/n)
区间:
[max(0, center-half), min(1, center+half)]
Z = (O - n*p) / sqrt(n*p*(1-p))
绝对值越大,说明与理论概率偏离越明显。
对全部奖项:
χ² = Σ (O-E)² / E
自由度近似:dof = 奖项数 - 1
后端默认窗口最近 50 抽:
- 全局率:
O_global / n_global - 最近率:
O_recent / n_recent - 差异:
|recent_rate - global_rate|
拟合:
y = a*x + b
x:抽奖序号y:累计积分
并计算 R²:
R² = 1 - SS_res / SS_tot
对每个可兑换道具,重复模拟 runs 次,估计“达成该道具”的成本分布。
规则:
- 抽到目标道具 -> 立即达成(抽中路径)
- 如果积分达到兑换门槛 -> 达成(兑换路径)
- 抽到非目标且可进暂存箱道具 -> 立即按配置分解,返还积分和钥匙
输出:
- 平均成本
- P50、P90
- 95% 经验分位区间(2.5% ~ 97.5%)
- 均值置信区间(
mean ± 1.96 * SE) - 抽中达成率、兑换达成率
- CDF 经验曲线点
规则:
- 忽略道具直中收益与道具分解收益
- 只靠抽到积分子项累计到兑换阈值
输出:
- 平均成本
- P50、P90
买钥匙选项有单价:
unit = price / keys_count
策略选择:
min_unit:选 unit 最小方案single_first:优先 10 元 1 抽bundle_first:优先 100 元 11 抽
后端会做一个经验模型:
avg_cost ≈ a*(1/p) + b
其中 p 是目标道具抽中概率,avg_cost 来自策略 A 的样本均值。
并给出:
- 相关系数
- 全样本
R² - 训练/测试切分(70/30)下 MAE、RMSE、R²
/api/config- 获取购买选项、奖池、兑换区、布局参数等
/api/state- 获取当前状态(含暂存明细、仓库明细)
/api/analysis- 获取统计分析结果
/api/simulate- 同步模拟(默认 500 次)
/api/simulate/status?job_id=...- 查询异步模拟任务状态
/api/db/items- 直接查看
prize_items全量行
- 直接查看
/api/buy- 入参:
{"option":"single"}或{"option":"bundle"}
- 入参:
/api/draw- 入参:
{"count":1}或{"count":10}
- 入参:
/api/stash/decompose- 入参:
{"item_id":"xxx","qty":1}
- 入参:
/api/stash/send- 入参:
{"item_id":"xxx","qty":1}
- 入参:
/api/stash/record-action- 入参:
{"record_id":123,"action":"send|decompose"}
- 入参:
/api/redeem- 入参:
{"item_id":"xxx","qty":1}
- 入参:
/api/state/reset- 重置用户状态
/api/simulate- 同步跑模拟,入参:
{"runs":500,"strategy":"min_unit"}
- 同步跑模拟,入参:
/api/simulate/start- 异步模拟,返回
job_id
- 异步模拟,返回
/api/config/reload- 前端“重载配置”触发(返回 ok;配置本身每次读取都来自 DB)
表:prize_items
item_id:唯一 ID(英文,建议稳定)name:显示名称item_type:item或points_childin_pool:是否参加抽奖(1/0)pool_weight:权重(越大越容易中)group_key:积分子项所属分组(仅points_child用)points_value:积分子项积分值exchange_points:兑换所需积分(>0 则进入兑换区)decompose_points:分解返还积分decompose_keys:分解返还钥匙direct_to_warehouse:是否直发仓库(1 直发,0 暂存)image_url:卡面图片popup_image_url:弹窗图card_bg_color:卡片背景palette_key:稀有色(red/orange/purple/blue/gray)redeem_limit_enabled:是否限兑redeem_limit_count:限兑数量redeem_tag_left、redeem_tag_right:兑换卡片角标sort_order:排序
建议先备份:
cp "/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/data/lottery.db" "/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/data/lottery.db.bak"SELECT item_id,name,item_type,in_pool,pool_weight,group_key,points_value,exchange_points,decompose_points,decompose_keys,direct_to_warehouse
FROM prize_items
ORDER BY sort_order,item_id;UPDATE prize_items
SET pool_weight = 0.35
WHERE item_id = 'legend_haojie';UPDATE prize_items
SET direct_to_warehouse = 1
WHERE item_id = 'king_stone';INSERT INTO prize_items(
item_id,name,item_type,in_pool,pool_weight,group_key,points_value,
exchange_points,decompose_points,decompose_keys,direct_to_warehouse,
image_url,popup_image_url,card_bg_color,palette_key,sort_order
) VALUES(
'points_30','30积分','points_child',1,3.0,'points_pool',30,
0,0,0,0,'','','','gray',207
);INSERT INTO prize_items(
item_id,name,item_type,in_pool,pool_weight,group_key,points_value,
exchange_points,decompose_points,decompose_keys,direct_to_warehouse,
image_url,popup_image_url,card_bg_color,palette_key,
redeem_limit_enabled,redeem_limit_count,redeem_tag_left,redeem_tag_right,sort_order
) VALUES(
'redeem_only_ak','AK47-兑换专属','item',0,0,NULL,0,
1688,0,0,1,'','','','gray',
1,1,'单大区限兑1','不可交易',400
);UPDATE prize_items
SET redeem_limit_enabled=1, redeem_limit_count=2
WHERE item_id='redeem_honor_soul';UPDATE popup_highlight_rules SET enabled=1 WHERE palette_key IN ('red','orange','purple','blue');
UPDATE popup_highlight_rules SET enabled=0 WHERE palette_key='gray';后端支持两种静态图路径:
/cf_images/...-> 读取项目根目录/cf_images//cfimages/...-> 读取/lottery_simulator/cfimages/
本项目现有素材主要在:
/Users/apple/Documents/PyCharm/浩劫艾丽莎/cf_images/
需要第三方库:
python3 -m pip install requests下载主图(在项目根目录执行):
cd "/Users/apple/Documents/PyCharm/浩劫艾丽莎"
python3 main.py下载 lotb 组图(建议在 cf_images 目录执行):
cd "/Users/apple/Documents/PyCharm/浩劫艾丽莎/cf_images"
python3 炫耀图片下载.py网页里点 重置模拟数据,或调用 /api/state/reset。
只影响 state.json,不会改奖池配置。
删除数据库文件后重启服务,后端会按内置默认值重建:
/Users/apple/Documents/PyCharm/浩劫艾丽莎/lottery_simulator/data/lottery.db
每次改 SQL 前备份两个文件:
lottery.dbstate.json
用环境变量改端口启动,例如:
CF_SIM_PORT=18082 python3 server.py先点击购买按钮补钥匙,再抽。
先点页面上的 重载后端配置,不行就刷新浏览器。仍不行时重启 server.py。
- 降低
模拟次数 - 避免把某些目标设置得过于极端(很高兑换门槛 + 很低概率)
- 每个目标最多 100000 步保护,不会无限循环
样本量小的时候,偏差、Z 值、趋势都容易抖动。多抽几百次后再观察更稳定。
- 后端无第三方框架,基于
http.server,结构简单,适合快速改逻辑。 - 前端是原生 JS + SVG 绘图,无构建工具。
- 异步模拟任务在内存里管理(
SIM_JOBS),服务重启后任务会丢失,这是预期行为。 - 线程安全依赖全局
LOCK+SIM_JOBS_LOCK,改并发逻辑时注意锁粒度。
- 本项目是本地概率模拟工具,不代表官方活动真实结果。
- 统计结论受样本量、配置和模型假设影响,不构成收益承诺。
- 所有道具发放均为本地数据演示,不接入真实账号资产系统。













