Python应用开发学习:如何让随机数按一定比列出现

一、前言

最近几个月我换了一批小游戏打发闲暇时间,其中《百炼英雄》这款游戏玩得有点上头。这款游戏培养英雄是提升战力的重点方法之一。我是零氪玩家,要想提升英雄实例,最主要的方式是通过游戏内的招募大厅去抽取常规英雄碎片,获得足够碎片后,才能提升英雄等级。

招募大厅中常驻英雄共有40多个,按卡片颜色分五个等级,分别是白卡、蓝卡、紫卡、橙卡、红卡。其中红卡和橙卡英雄更好,也是我们日常最愿意抽的卡。抽卡的大致流程是进入招募大厅后,会随机出现3名英雄(每30分钟自动刷新),第一次招募用金币,由系统随机抽取,抽到1名英雄后,再要招募第2名和第3名英雄就要使用绿钻。三次招募完成自动刷新英雄(第一次招募和第二次招募后可以点放弃,直接刷新)。

理论上第一次招募获得自己想要的英雄的概率是33%,第二次为50%,第三次为100%。但我玩了2个多月,在游戏大厅内招募了1万多次,发现实际情况并非如此。这个游戏的刷新机制和抽卡机制都不是普通的随机模式,内部是做了一些规则设定。基本上是想要的英雄出现少,即使出现了也很难用金币抽中。尤其是红卡,特别明显,要想抽中心仪的红卡,往往要用绿钻抽,且很多情况要在第三次才能抽中。对于零氪玩家来说绿钻的获得难度比金币大得多,这就造成了主力英雄等级提升难的局面。

游戏中提供的招募机制信息极少,我只好花了一些时间记录近2400次抽卡数据,并对记录数据进行统计,希望能对这款游戏的英雄招募机制有所了解。

|-----------|-------|-------|-------|-------|-------|-------|---|
| | 白卡 | 蓝卡 | 紫卡 | 橙卡 | 红卡 | 小计 | |
| 出现次数 | 1047 | 3204 | 2280 | 513 | 130 | 7174 | |
| 出现次数占比 | 14.6% | 44.7% | 31.8% | 7.2% | 1.8% | | |
| | | | | | | | |
| 抽中次数 | 473 | 1111 | 637 | 141 | 35 | 2397 | |
| 抽中次数占比 | 19.7% | 46.3% | 26.6% | 5.9% | 1.5% | | |
| 抽中次数/出现次数 | 45.2% | 34.7% | 27.9% | 27.5% | 26.9% | 33.4% | |

备注:少数时候用了绿钻抽英雄,会减少英雄刷新次数,因此出现次数小计<抽中次数小计*3

有了这组数据,我打算自己编写一段代码,模拟百炼英雄的抽卡机制,获得类似的效果。

二、遇事不决问AI

要实现上述效果,关键是如何让英雄刷新和抽卡两个随机动作均具有一定的权重,使得结果呈概率性分布。这个问题就交给AI解决吧!

我整理了一下思路后,向AI提出问题,得到了我想要的答案。

通过AI提供的答案,我才知道以前常用的random模块中有相关的方法能够实现这一功能。

random.choices(population, weights=None, *, cum_weights=None, k=1) ‌:

  • population:用于抽取元素的数据集,如列表、元组。
  • weights:对应每个元素的相对权重,单位是%。
  • k:表示抽取的次数。如果只需要抽取一个元素,设为 1。

choices 返回的是列表

三、实现功能

知晓了关键方法,后面的事情就好办多了。

(一)三个关键数据

1. card_type = ('w', 'b', 'p', 'o', 'r')

这组数据代表了英雄卡类型,依次是:白卡、蓝卡、紫卡、橙卡、红卡。

2. card_weights = (15, 44, 32, 7, 2)

这组数据表示招募大厅刷新时,各类型英雄卡出场的概率,单位是%。此数据参考了我统计的招募英雄的出现次数占比。

3. draw_probability = (45, 35, 28, 25, 20)

这组数据表示各类型英雄卡出场后,被抽中的概率,单位是%。此数据参考了我统计的招募英雄的 抽中次数/出现次数。

(二)代码逻辑

本段代码的主要逻辑都在方法continuous_draws_test中。该方法生成的数据保存在appear、random_results、weighted_results、export_list四个列表中。

1.该方法可以进行多次连续抽卡的模拟,抽卡次数由参数number传递,通过for循环进行控制。

2.每次抽卡前先通过random.choices方法从card_type中抽取3个元素(英雄卡类型),权重参数用card_weights。获得的结果保存在random_selection中。

游戏中每次刷新除了确定英雄卡类型外,还要确定具体的英雄,本代码只随机抽取英雄卡类型,不考虑如何确定具体英雄。获得的结果保存在random_draw中。

3.抽取到英雄后通过random.choice方法从3个元素中随机抽取1个元素,这个方法没有权重参数,属于普通随机方式,是作为对比的参照数据。

4.另外还使用random.choices方法从random_selection的3个元素中抽取1个元素,这个方法带有权重参数,是模拟百炼英雄的抽卡效果。

但这个方法的权重参数不能直接用draw_probability,因为要抽取的数据集random_selection中只有3个元素,而draw_probability中是5个元素,数量上不匹配。且random_selection中的3个元素,每次都不一样,因此本处random.choices方法使用的权重参数要根据random_selection的内容做调整。

这一步通过以下代码实现:

python 复制代码
pro = []
for x in range(3):
    index = style.index(random_selection[x])  # 获得random_selection中各元素在style中的索引
    pro.append(probability[index])  # 利用上一步的索引从probability提取数据并添加到pro中

5.对出场英雄、普通随机模式抽卡、加权随机模式抽卡数据进行统计。

appear、random_results、weighted_results、export_list四个列表中的数据,要先用Counter方法进行处理,然后通过for循环分别计算各类卡出现的概率。

6.刷新和抽卡的原始数据通过pandas模块导出到xlsx文件中,便于后续对原始数据进行进一步的统计分析。

软件编好后,我进行了几次测试,得到的结果如下:

|-----|---|------------------------------------------------------------------------|
| 第1次 | | 出场英雄数量统计: Counter({'b': 1332, 'p': 955, 'w': 464, 'o': 195, 'r': 54}) |
| | | w: 15.47% |
| | | b: 44.40% |
| | | p: 31.83% |
| | | o: 6.50% |
| | | r: 1.80% |
| | | 随机模式抽卡数量统计: Counter({'b': 433, 'p': 320, 'w': 163, 'o': 66, 'r': 18}) |
| | | w: 16.30% |
| | | b: 43.30% |
| | | p: 32.00% |
| | | o: 6.60% |
| | | r: 1.80% |
| | | 加权模式抽卡数量统计: Counter({'b': 489, 'p': 263, 'w': 192, 'o': 42, 'r': 14}) |
| | | w: 19.20% |
| | | b: 48.90% |
| | | p: 26.30% |
| | | o: 4.20% |
| | | r: 1.40% |
| | | |
| 第2次 | | 出场英雄数量统计: Counter({'b': 1300, 'p': 950, 'w': 473, 'o': 217, 'r': 60}) |
| | | w: 15.77% |
| | | b: 43.33% |
| | | p: 31.67% |
| | | o: 7.23% |
| | | r: 2.00% |
| | | 随机模式抽卡数量统计: Counter({'b': 433, 'p': 316, 'w': 158, 'o': 71, 'r': 22}) |
| | | w: 15.80% |
| | | b: 43.30% |
| | | p: 31.60% |
| | | o: 7.10% |
| | | r: 2.20% |
| | | 加权模式抽卡数量统计: Counter({'b': 455, 'p': 280, 'w': 187, 'o': 64, 'r': 14}) |
| | | w: 18.70% |
| | | b: 45.50% |
| | | p: 28.00% |
| | | o: 6.40% |
| | | r: 1.40% |
| | | |
| 第3次 | | 出场英雄数量统计: Counter({'b': 1275, 'p': 937, 'w': 496, 'o': 213, 'r': 79}) |
| | | w: 16.53% |
| | | b: 42.50% |
| | | p: 31.23% |
| | | o: 7.10% |
| | | r: 2.63% |
| | | 随机模式抽卡数量统计: Counter({'b': 427, 'p': 296, 'w': 169, 'o': 78, 'r': 30}) |
| | | w: 16.90% |
| | | b: 42.70% |
| | | p: 29.60% |
| | | o: 7.80% |
| | | r: 3.00% |
| | | 加权模式抽卡数量统计: Counter({'b': 440, 'p': 289, 'w': 196, 'o': 57, 'r': 18}) |
| | | w: 19.60% |
| | | b: 44.00% |
| | | p: 28.90% |
| | | o: 5.70% |
| | | r: 1.80% |
| | | |
| 第4次 | | 出场英雄数量统计: Counter({'b': 1326, 'p': 951, 'w': 462, 'o': 201, 'r': 60}) |
| | | w: 15.40% |
| | | b: 44.20% |
| | | p: 31.70% |
| | | o: 6.70% |
| | | r: 2.00% |
| | | 随机模式抽卡数量统计: Counter({'b': 446, 'p': 319, 'w': 151, 'o': 61, 'r': 23}) |
| | | w: 15.10% |
| | | b: 44.60% |
| | | p: 31.90% |
| | | o: 6.10% |
| | | r: 2.30% |
| | | 加权模式抽卡数量统计: Counter({'b': 463, 'p': 280, 'w': 180, 'o': 60, 'r': 17}) |
| | | w: 18.00% |
| | | b: 46.30% |
| | | p: 28.00% |
| | | o: 6.00% |
| | | r: 1.70% |
| | | |
| 第5次 | | 出场英雄数量统计: Counter({'b': 1257, 'p': 1025, 'w': 470, 'o': 193, 'r': 55}) |
| | | w: 15.67% |
| | | b: 41.90% |
| | | p: 34.17% |
| | | o: 6.43% |
| | | r: 1.83% |
| | | 随机模式抽卡数量统计: Counter({'b': 423, 'p': 355, 'w': 153, 'o': 53, 'r': 16}) |
| | | w: 15.30% |
| | | b: 42.30% |
| | | p: 35.50% |
| | | o: 5.30% |
| | | r: 1.60% |
| | | 加权模式抽卡数量统计: Counter({'b': 414, 'p': 316, 'w': 194, 'o': 66, 'r': 10}) |
| | | w: 19.40% |
| | | b: 41.40% |
| | | p: 31.60% |
| | | o: 6.60% |
| | | r: 1.00% |

从上述测试数据来看,普通随机模式获得的各类卡的抽中概率与英雄刷新时各卡出现概率近似。而加权随机模式获得的各类卡的抽中概率则更接近我统计的百炼英雄的抽中次数占比。这组数据,基本实现了模拟效果。

四、代码展示

最后,放出我写的代码共参考

python 复制代码
import random
from collections import Counter
import pandas as pd

card_type = ('w', 'b', 'p', 'o', 'r')   # 英雄类型:白卡、蓝卡、紫卡、橙卡、红卡
card_weights = (15, 44, 32, 7, 2)   # 各个类型英雄卡出场的概率,单位是%
draw_probability = (45, 35, 28, 25, 20)  # 各个类型英雄卡出场后,被抽中的概率,单位是%


def continuous_draws_test(number, style, weights, probability, quota=3):
    """
    两种模式(随机、加权)连续抽卡对比测试
    :param number: 总的抽卡次数
    :param style: 英雄卡类型
    :param weights: 英雄卡出场概率
    :param probability: 英雄卡被抽卡概率
    :param quota: 每次抽出卡片数

    """
    appear = []  # 出场英雄记录
    random_results = []  # 随机抽卡记录
    weighted_results = []  # 加权抽卡记录
    export_list = []  # 用于将结果导出到excel中
    print("开始连续抽取...")
    for i in range(number):
        # 一次随机获得3个元素, k=3 表示一次性抽取3个元素(模拟刷新英雄)
        random_selection = random.choices(style, weights=weights, k=quota)
        # 从获得的3个元素中随机抽取1个(作为对比的普通随机抽卡效果)
        random_draw = random.choice(random_selection)
        # 从获得的3个元素中使用加权概率抽取1个(模拟百炼英雄的抽卡效果)
        pro = []
        for x in range(3):
            index = style.index(random_selection[x])  # 获得random_selection中各元素在style中的索引
            pro.append(probability[index])  # 利用上一步的索引从probability提取数据并添加到pro中
        weighted_draw = random.choices(random_selection, weights=pro, k=1)
        # 将本轮结果添加到列表中
        appear.extend(random_selection)
        random_results.extend(random_draw)
        weighted_results.extend(weighted_draw)
        export_list.append(random_selection + list(random_draw) + list(weighted_draw))

    # 统计结果
    count_appear = Counter(appear)
    print('出场英雄数量统计:', count_appear)
    for s in style:
        probability = count_appear[s] / (number * 3) * 100
        print(f"  {s}: {probability:.2f}%")

    count_random = Counter(random_results)
    print('随机模式抽卡数量统计:', count_random)
    for s in style:
        probability = count_random[s] / (number * 1) * 100
        print(f"  {s}: {probability:.2f}%")

    count_weighted = Counter(weighted_results)
    print('加权模式抽卡数量统计:', count_weighted)
    for s in style:
        probability = count_weighted[s] / (number * 1) * 100
        print(f"  {s}: {probability:.2f}%")

    # 导出原始数据到excel中
    df = pd.DataFrame(export_list, columns=['出场1', '出场2', '出场3', '随机抽取', '加权抽取'])
    df.to_excel('抽卡模拟测试.xlsx', index=False)


# 执行测试程序
continuous_draws_test(1000, card_type, card_weights, draw_probability, 3)
相关推荐
amongdata1 小时前
MyVoiceTyping:面向中文语音输入的本地优先开源方案
python·开源·音视频·语音识别
薛定猫AI1 小时前
【技术干货】大模型检查点可靠性评测:基于 Python 构建幻觉与指令遵循测试
开发语言·python
渣渣灰飞1 小时前
MySQL 系统学习 第六阶段:Redis + 缓存 + 高并发设计 第八章:Redis 高级应用
学习·mysql·缓存
Kobebryant-Manba2 小时前
学习Bert
学习·自然语言处理·bert
渣渣灰飞2 小时前
MySQL 系统学习 第六阶段:Redis + 缓存 + 高并发设计 第十章:Redis 综合企业项目实战
学习·mysql·缓存
Mr__Miss2 小时前
Java 随机数生成器选型指南:Random、ThreadLocalRandom 与 SecureRandom
java·开发语言·python
CTA量化套保2 小时前
2026年下半年Python量化入门,先看API如何接进小流程
人工智能·python
薛定猫AI2 小时前
【深度解析】动态多智能体团队:并行规划、开发与验证的 Python 实战
大数据·开发语言·python
xian_wwq2 小时前
【学习笔记】多模态部署:VLM、语音、视频理解(29/35)
笔记·学习·音视频