背景

现在游戏都有抽卡,抽卡概率测试怎么尽可能去保证正确?
本文不讲为何要做统计,抽卡随机种子怎么产生,随机原理又如何等等,我不太会讲这些,如果有兴趣,请参考https://zhuanlan.zhihu.com/p/263526568
本文主要内容:基于我个人目前的测试环境,带你实操做一次入门级抽卡概率统计(其实就是把以前做过的事,记录一遍)。

方案思索

最理想的方案:多客户端并发抽卡,服务器日志记录,想办法获取数据,最后统计数据。这是最切合玩家的实际情况:几乎所有游戏,都会在卡池开放的那一刻,大量玩家涌入抽卡。多客户端并发旨在确保服务器随机数产生是线程安全的;服务器日志能很方便的记录所有的抽卡结果。
实际情况:目前有多并发协议工具,个人维护,本机电脑很难并发多个号执行抽卡(小几个倒是可以,毕竟服务器也是搭在我这台电脑);服务器日志,没有,问程序,答曰:你就抽卡能抽到,抽的问题不大就不用管了,日志不加。是的,你没看错,就是这么个答复。小公司就一个测试,你还能指望从程序那获得什么尊重与帮助(日常被某个程序鄙视)。
故采用另一种 “不太靠谱” 的曲线救国方式:
1:客户端自己收集 log。有一说一,每个游戏项目内网的包,肯定是有控制台打印收发协议数据的,因此这个貌似也可行;
2:客户端多开,然后模拟人工点击去生产足够的抽卡数据(当然,单开客户端来测试,也是有必要的,比如可以关注到单个玩家获取 SSR 的难度,最长多少抽不出 SSR。多开客户端模拟(假的)并发来做最后的概率计算,则更可靠一些。)
3:利用 pandas 以及 matplotlib 处理数据,生成我需要的图片
话不多说,接下来请看实操过程。

log 收集

我拥有的客户端代码是经过编译混淆的,但是仍然可以在 F12 控制台 sources 的 js 中找到代码并注入新的代码。

收到消息,以及发送协议这几个字,肯定是写在代码中的,于是搜索关键中文找到对应位置,并在对应位置注入我写的代码。
忽然发现我不太会 js,写起来费时间。于是我再绕个弯,注入一下代码:

static posthttp(data){
            var xhr = new XMLHttpRequest()
            xhr.open('POST', 'http://这里是ip地址:8888/api/v1/user', false);
            xhr.send(String(data)); 
        }

一个极其简单的 post,以及另外搭了一个极其简单的服务器来接受数值。
接下来在客户端 js 中根据协议号以及协议数据筛选出我要的数据:

if (是我要的协议号){
                for (x in t[1].drawingIds){
                    // u.Log("中文牛逼",  t[1].drawingIds[x]);    PS:是的,中文很牛逼。如果你说不牛逼,好,那么你是对的。
                    myArray.splice(0,0,t[1].drawingIds[x])
                }    

在我的服务器处理一下数据
处理类 class APIUserHandler
处理方法 post
把抽卡的 id 处理转换成最终的抽卡的品质(sr,r,ssr)。
我是做的十连抽卡(确认过就是单抽循环十次),我直接把十连的数据处理为 DataFrame 数据并写入文件(预计想搞个几十万数据,所以写入 csv 是比较合适的)

df = pd.DataFrame(np.array(temp))
df.to_csv('D:/Users/User/PycharmProjects/untitled/test.csv', mode='a', index=False, header=False)

接下来模拟点击几万次:如果你很牛逼,建议你手点;如果你非常牛逼,可以搞按键精灵;如果你和我一样菜的扣脚,自己写一个。
先前已经有了点击脚本,双线程,支持 esc 停止程序。恩,以前写的,拿过来用。


两个客户端两个点击位置。(别和我说你游戏 debug 包没有提供输入发送协议或者提供指令的功能,不然你怎么做协议测试?)
点第一个客户端再点第二个客户端,切换着点,搞出 20 万条数据(两个号一起抽卡获得的数据)如下:

数据处理

我需要得到指定品质出现两次的(次数)差值,比如第 5 次抽出了 SSR,第 90 次又出了 SSR,差值为 85;同时计算差值出现的次数

data = pd.read_csv('D:/Users/User/PycharmProjects/untitled/test.csv')
data_list = data['0'].values.tolist()#我往测试数据手动加入0作为colunm name,方便直接取出pd数据
def difference(value, datalist):
    j = 0
    result = []
    for i in range(0, len(datalist)):
        if datalist[i] == value:
            if i-j == 100:
                print(i)
            result.append(i-j)
            j = i
    return result

接下来出图,恩,ssr,r,sr 一起画的图太丑了(差值太多),于是我干脆不组合了,单独先看下 ssr。
关键代码

df2 = pd.DataFrame(np.array(ssr), columns=['ssr'])
result2 = df2.apply(pd.value_counts).sort_index()
result4 = pd.concat([result2], axis=1)
result4.plot(kind='bar', figsize=(20, 10), rot=0)
y_major_locator=MultipleLocator(10)
x_major_locator = MultipleLocator(10)
ax=plt.gca()
ax.xaxis.set_major_locator(x_major_locator)
ax.yaxis.set_major_locator(y_major_locator)

出图例子(其他图,散图等,不列举):

组合图示例:

最后:计算概率综合看下是否有问题;单个玩家收集数据看下最长多少次不出 ssr(上图两个玩家最多合计 155 抽不出 ssr,但是单个玩家不能看这个);r 卡条形图,sr 条形图会不会有什么异常数据等等。
综上:就是玩(不负责靠谱不靠谱)。为啥,我一个人测不来,策划没找我确认概率,直接找程序,程序就随便模拟了几万次给了个概率说没问题。
所以自己玩呗,加班有空弄点数据做个图,自己最后确认概率没问题就行,有问题再找程序(其实还有其他保底设定,幸运值什么的,也是经过反复测试确认没问题了)。

注:实际抽卡测试不只是做数据统计,要做的很多;另外战斗概率测试则更复杂(客户端混淆的代码偶尔也可以被我拿来做白盒测试)。


↙↙↙阅读原文可查看相关链接,并与作者交流