上一期文章我们实现了比较完善的单次抽奖函数,成功地在默认卡池中抽取单个结果,并且解决了一个微小但致命的问题,今天我们着手进行《明日方舟》干员寻访模拟器的实现,如果您忘记了上一期的内容,或者是第一次阅读“游戏抽奖模拟器专栏”的文章,您可以快速跳转前几期的文章:

  1. 第一期
  2. 第二期(上一期)

给大家重温一下抽奖规则(如果您已经了解规则,可以跳过这些内容):
1、基准概率
六星干员出率:2%,五星干员出率:8%,四星干员出率:50%,三星干员出率:40%,不会出现一、二星干员。

2、默认卡池
六星干员20名,包括暂时绝版的干员1名;五星干员37名;四星干员29名;三星干员16名。共计102名干员(含绝版一名)。
每次抽奖在101位非绝版干员中随机获取一名,那么各干员在其所属星级内等可能出现。

3、官方每更新一个卡池,该卡池用户前十次抽奖内必有一次是五星或六星干员,具体第几次为随机。

4、官方的标准卡池(而非默认卡池)含有两名特定六星干员,三名特定五星干员,特定干员在所属星级内的出率总和占该星级出率的50%,也就是说,如果你抽中了六星干员,有50%概率抽到两名特定六星中的一个,具体是随机的,并且是等可能的,但也有50%概率抽到别的非特定干员。

5、如果用户在任何一个卡池连续50次都没有抽到六星干员,下一次六星干员的总出率将提高两个百分点,之后每一次都会提升两个百分点,直到抽到六星,将恢复基准概率。这个次数不会因卡池变换而清零。

6、每次抽奖消耗600合成玉,合成玉与另一种货币:至纯源石的换算规律是:1源石=180合成玉

上一期我们实现了单次抽奖函数:

def single():global real_no_six,least,do_leastno_six_times=real_no_sixif least!=0:a=random.randint(1,least)if a==1:least=0#触发保底机制后清除剩余的次数do_least=Trueelse:least-=1do_least=Falsewhile no_six_times>=50:if do_least==True:if real_no_six<54:least_stars[(no_six_times-50)*2]=6least_stars[(no_six_times-50)*2+1]=6else:stars[(no_six_times-50)*2]=6stars[(no_six_times-50)*2+1]=6no_six_times=no_six_times-1if do_least==True:choices=random.choice(least_stars)else:choices=random.choice(stars)if choices==6:result=random.choice(six_stars)real_no_six=0stars=[4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,5,5,5,5,5,5,5,5,6,6]do_least=False#无论通过什么方式抽到六星,都要解除保底least=0print(result)#这里简写了,下同elif choices==5:result=random.choice(five_stars)real_no_six+=1do_least=Falseleast=0print(result)elif choices==4:result=random.choice(four_stars)real_no_six+=1print(result)else:result=random.choice(three_stars)real_no_six+=1print(result)

今天我们继续实现第四条中的内容:标准卡池

标准卡池中有两名特定六星干员,三名五星干员,具体是什么干员,最好是让用户自定义的,因为谁也不知道官方每两个星期会更新什么卡池,我们应当随着官方的变化而变化,所以在这里卡池机制的自由度非常重要

规则要求是让两名六星干员出率总和占全六星的50%,相应地,其他六星干员的出率就会下降,这和上一期的“50次提概率”的想法是一样的。

但是,这个机制和“50次提概率”的实现细节上有很大区别:“50次提概率”时,为了让六星的出现概率提升,允许将其他事件的概率降至0。而今天的卡池机制不能为了提升特定干员的概率,让其他干员不出现。所有可能的干员都必须出现

第二个区别就是,“50次出一次”机制中六星占全星级概率是随着抽奖情况变化的,而卡池机制中特定干员占该星级干员的比例一旦根据用户设置确定,就不会再改变。

那我们该如何提升特定干员的概率呢?我们先来看个实验:

events=['a','b','c','d','e']
print(events.count('a')/len(events))
#用count输出'a'的出现次数,除以总数量,得到概率
events.append('a')
print(events.count('a')/len(events))#输出结果:
0.2
0.333333333333333333

这是一个十分基础的东西,是最原始的概率提升的方法,除了特定的a以外,其它事件的出现概率都是均等的,这样的好处是能够等量地降低非特定事件的概率,每个人抽出一点点去提升特定事件的概率,保证了抽奖的公平性。

按照这个思路,如果我们在列表的末端加上需要提升概率的干员的事件,我们就可以实现提升概率。

six_stars=['黑','能天使','莫斯提马','艾雅法拉','伊芙利特','刻俄柏','斯卡蒂','煌','陈','银灰','赫拉格','阿','推进之王','安洁莉娜','麦哲伦','星熊','塞雷娅','夜莺','闪灵','风笛']
five_stars=['守林人','陨星','灰喉','白金','送葬人','普罗旺斯','蓝毒','夜魔','惊蛰','天火','布洛卡','拉普兰德','星极','诗怀雅','幽灵鲨','芙兰卡','狮蝎','食铁兽','崖心','槐琥','红','凛冬','德克萨斯','苇草','初雪','格劳克斯','真理','空','梅尔','雷蛇','临光','可颂','吽','白面鸮','赫默','华法琳','慑砂']
four_stars=['杰西卡','梅','流星','安比尔','白雪','红云','夜烟','远山','角峰','调香师','末药','苏苏洛','蛇屠箱','古米','霜叶','缠丸','猎蜂','慕斯','杜宾','阿消','暗索','砾','地灵','深海色','清道夫','桃金娘','红豆','宴','格雷伊']
three_stars=['炎熔','史都华德','克洛丝','空爆','月见夜','泡普卡','玫兰莎','香草','翎羽','芬','卡缇','米格鲁','斑点','芙蓉','安赛尔','梓兰']
#以上为默认卡池的参考#以下是处理卡池的方案
cha6_1=input('请输入特定六星干员姓名:')
cha6_2=input('请输入第二六星干员姓名:')
cha5_1=input('请输入特定五星干员姓名:')
cha5_2=input('请输入第二五星干员姓名:')
cha5_3=input('请输入第三五星干员姓名:')
while six_stars.count(cha6_1)+six_stars.count(cha6_2)<0.5*len(six_stars):six_stars.extend([cha6_1,cha6_2])
while five_stars.count(cha5_1)+five_stars.count(cha5_2)+five_stars.count(cha5_3)<0.5*len(five_stars)five_stars.extend([cha5_1,cha5_2,cha5_3])
#不要用append,否则你得写五行,倘若要是有很多特定干员呢,累死你#测试结果
请输入特定六星干员姓名:银灰
请输入第二六星干员姓名:艾雅法拉
请输入特定五星干员姓名:可颂
请输入第二五星干员姓名:灰喉
请输入第三五星干员姓名:拉普兰德
>>> (six_stars.count('银灰')+six_stars.count('艾雅法拉'))/len(six_stars)
0.5272413793103449

这样做的好处是非常方便,对于官方标准卡池是足够了,你只需要输入必要的干员的名字!不过你也看到了:这个概率并不是很准,有时候会偏差到0.56,取决于你卡池的大小,卡池项目越多,出现概率就越准确。或者你可以编写一个按概率抽奖的函数,可参考我发布的一篇博客:
学会编一个按概率抽奖的函数!Python3实现为每个随机事件指定一个抽奖概率

缺点:由于Python浮点数计算精度的问题,上述链接编写的函数需要自己另行计算概率,而且可能因为精度问题造成所有概率加起来不等于1,所以无论是哪种方法,任何概率都是不可能完全准确的

代码块中的函数功能限制很大,只能设置两个特定干员和三个五星干员,只适用于官方的标准寻访卡池,而对于官方特别公布限时寻访卡池(可能是更多或者更少特定干员),显然是不适用的。

那我们如何才能做到模拟官方的限时寻访卡池呢?我们需要创建一个自由度更高的函数,用来进行自由度更高的概率提升:

def free_setter():num6=input('您要选择几名六星干员?')num5=input('您要选择几名五星干员?')if num6=='1':print('请输入六星干员的姓名')name6_1=input()while six_stars.count(name6_1)<0.5*len(six_stars):six_stars.extend([name6_1])elif num6=='2':print('请输入两个六星干员的姓名')name6_1=input()name6_2=input()while six_stars.count(name6_1)+six_stars.count(name6_2)<0.5*len(six_stars):six_stars.extend([name6_1,name6_2])
#然后是num6=3或者4或者5...
#再后来就是五星的...

方法的缺点非常明显,需要事先知道你要提升多少个干员,自己编写代码也非常麻烦,任何一种情况都要给出不同的提升方法。而且只能支持至多5个干员,支持的数量越多,写的代码也越多。

那有什么办法可以自动检测干员数量,最大限度支持更多干员数目呢?我们可以我们可以自己设计一个特别的函数,用来实行特殊计数方法,并且修改代码块执行条件。

这样的思路是直接让用户输入所有六星干员的姓名,让用户用分号分隔,然后直接对用户输入的字符串按分号进行split分解,分解出来的列表就是需要提升的干员,我们假设它叫b,我们还得计算b中所有元素在a中的出现次数总和,这样就能精准的控制干员的出现概率。能想到这一步比较困难,但是一旦成功,等待你的就是简洁的代码:

ganyuan6=input('请输入需要提升概率的六星干员名单(英文分号分隔):')
ganyuan5=input('请输入需要提升概率的五星干员名单(英文分号分隔):')
special_six_star=ganyuan6.split(';')
special_five_star=ganyuan5.split(';')
#以下九行为作者自行研究而成
def count(b,a):#计算b中所有元素在a中出现次数总和counts=0for i in b:counts+=a.count(i)return counts
while count(special_six_star,six_stars)<0.5*len(six_stars):six_stars.extend(special_six_star)
while count(special_five_star,five_stars)<0.5*len(five_stars):five_stars.extend(special_five_star)

上述方法只用了很少的代码,就实现了自适应干员个数,自适应添加概率和自由度更高的功能,主要是count函数五行代码立了大功,能够有这样的构思,需要较长时间的编程经验和较广的功能知识储备,本人使用这种方法将121行代码缩减为9行。

但是有某些特殊的限时寻访卡池,我们刚才的函数造不出来,比如这个:

这个是特选干员定向寻访,你在这个卡池只可能抽到特定的六星干员,也就是说,四名六星干员出率占全六星出率的100%,六名五星干员占全五星出率的100%,当然,寻访的根本规则还是不变的,六星2%,五星8%,四星50%,三星40%。

而我们的函数默认是占该星级50%,为了模拟这种卡池,我们让用户自己输入占比,检查合理之后,我们需要让程序判定是否占比为1:

def count(b,a):#计算b中所有元素在a中出现次数总和counts=0for i in b:counts+=a.count(i)return counts
def free_setter():global six_stars,five_stars
#也可以改成 def free_setter(ganyuan6,ganyuan5,r6=0.5,r5=0.5)
#但是用户不知道你的函数是怎么样的,最终在程序中需要引导用户输入
#所以相比之下,在程序开发初期,使用这里写的函数是对用户非常友好的ganyuan6=input('请输入需要提升概率的六星干员名单(英文分号分隔):')r6=input('请输入特定六星干员出率总和占全六星的比值:')ganyuan5=input('请输入需要提升概率的五星干员名单(英文分号分隔):')r5=input('请输入特定五星干员出率总和占全六星的比值:')if not (0.3<=r6<=1 and 0.3<=r5<=1):print('占比数值设置不合理!')returnspecial_six_star=ganyuan6.split(';')special_five_star=ganyuan5.split(';')if r6==1:#如果六星干员占比是1,那么把卡池直接改为用户输入的内容,比较方便six_stars=special_six_starelse:while count(special_six_star,six_stars)<0.5*len(six_stars):six_stars.extend(special_six_star)if r5==1:#五星也是一样five_stars=special_five_starelse:while count(special_five_star,five_stars)<0.5*len(five_stars):five_stars.extend(special_five_star)print('完毕')#试验结果
>>> free_setter()
请输入需要提升概率的六星干员名单(英文分号分隔):银灰;陈;艾雅法拉;伊芙利特
请输入特定六星干员出率总和占全六星的比值:1
请输入需要提升概率的五星干员名单(英文分号分隔):白面鸮;德克萨斯;白金;可颂;狮蝎;诗怀雅
请输入特定五星干员出率总和占全六星的比值:1
完毕
>>> six_stars
[银灰, 陈, 艾雅法拉, 伊芙利特]
>>> five_stars
[白面鸮, 德克萨斯, 白金, 可颂, 狮蝎, 诗怀雅]
>>>

这样就能够模拟定向寻访的卡池,更加贴近了游戏的实际。

如果你想了解更多这个游戏卡池的相关信息,你可以查阅以下两个站点:
http://ak.mooncell.wiki/w/卡池一览/常驻标准寻访
http://ak.mooncell.wiki/w/卡池一览/限时寻访
这个网站能够查阅到的东西远比这里推荐的多,如果需要测试函数,进一步模拟游戏的话,你可以拿这些数据练练手。

如果我们要重置这个卡池,恢复到原来的卡池应该怎么办呢?没错,我们应该将初始的卡池先储存在另一个变量中,之后重置的时候只要让卡池变量指向初始卡池所在的变量,就能实现卡池重置:

initial_six=['黑','能天使','莫斯提马','艾雅法拉','伊芙利特','刻俄柏','斯卡蒂','煌','陈','银灰','赫拉格','阿','推进之王','安洁莉娜','麦哲伦','星熊','塞雷娅','夜莺','闪灵','风笛']
initial_five=['守林人','陨星','灰喉','白金','送葬人','普罗旺斯','蓝毒','夜魔','惊蛰','天火','布洛卡','拉普兰德','星极','诗怀雅','幽灵鲨','芙兰卡','狮蝎','食铁兽','崖心','槐琥','红','凛冬','德克萨斯','苇草','初雪','格劳克斯','真理','空','梅尔','雷蛇','临光','可颂','吽','白面鸮','赫默','华法琳','慑砂']
initial_four=['杰西卡','梅','流星','安比尔','白雪','红云','夜烟','远山','角峰','调香师','末药','苏苏洛','蛇屠箱','古米','霜叶','缠丸','猎蜂','慕斯','杜宾','阿消','暗索','砾','地灵','深海色','清道夫','桃金娘','红豆','宴','格雷伊']
initial_three=['炎熔','史都华德','克洛丝','空爆','月见夜','泡普卡','玫兰莎','香草','翎羽','芬','卡缇','米格鲁','斑点','芙蓉','安赛尔','梓兰']
six_stars=initial_six
five_stars=initial_five
four_stars=initial_four
three_stars=initial_threedef reset():global no_six_times,least,six_stars,five_stars,four_stars,three_starsno_six_times=0#系第二期“50次加概率”least=0#系第二期“10次出一次”保底six_stars=initial_sixfive_stars=initial_fivefour_stars=initial_fourthree_stars=initial_threedef free_setter():global six_stars,five_starsganyuan6=input('请输入需要提升概率的六星干员名单(英文分号分隔):')r6=input('请输入特定六星干员出率总和占全六星的比值:')ganyuan5=input('请输入需要提升概率的五星干员名单(英文分号分隔):')r5=input('请输入特定五星干员出率总和占全六星的比值:')if not (0.3<=r6<=1 and 0.3<=r5<=1):print('占比数值设置不合理!')returnspecial_six_star=ganyuan6.split(';')special_five_star=ganyuan5.split(';')if r6==1:#如果六星干员占比是1,那么把卡池直接改为用户输入的内容,比较方便six_stars=special_six_starelse:while count(special_six_star,six_stars)<0.5*len(six_stars):six_stars.extend(special_six_star)if r5==1:#五星也是一样five_stars=special_five_starelse:while count(special_five_star,five_stars)<0.5*len(five_stars):five_stars.extend(special_five_star)print('完毕')
###################################################
def single():#上一期实现的单抽函数global real_no_six,least,do_leastno_six_times=real_no_sixif least!=0:a=random.randint(1,least)if a==1:least=0#触发保底机制后清除剩余的次数do_least=Trueelse:least-=1do_least=Falsewhile no_six_times>=50:if do_least==True:if real_no_six<54:least_stars[(no_six_times-50)*2]=6least_stars[(no_six_times-50)*2+1]=6else:stars[(no_six_times-50)*2]=6stars[(no_six_times-50)*2+1]=6no_six_times=no_six_times-1if do_least==True:choices=random.choice(least_stars)else:choices=random.choice(stars)if choices==6:result=random.choice(six_stars)real_no_six=0stars=[4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,5,5,5,5,5,5,5,5,6,6]do_least=False#无论通过什么方式抽到六星,都要解除保底(如果有)least=0print(result)#这里简写了,下同elif choices==5:result=random.choice(five_stars)real_no_six+=1do_least=Falseleast=0print(result)elif choices==4:result=random.choice(four_stars)real_no_six+=1print(result)else:result=random.choice(three_stars)real_no_six+=1print(result)>>> free_setter()
请输入需要提升概率的六星干员名单(英文分号分隔):银灰;陈;艾雅法拉;伊芙利特
请输入特定六星干员出率总和占全六星的比值:1
请输入需要提升概率的五星干员名单(英文分号分隔):白面鸮;德克萨斯;白金;可颂;狮蝎;诗怀雅
请输入特定五星干员出率总和占全六星的比值:1
完毕
>>> six_stars
[银灰, 陈, 艾雅法拉, 伊芙利特]
>>> five_stars
[白面鸮, 德克萨斯, 白金, 可颂, 狮蝎, 诗怀雅]
>>> reset()
>>> six_stars
[银灰, 陈, 艾雅法拉, 伊芙利特]
>>> five_stars
[白面鸮, 德克萨斯, 白金, 可颂, 狮蝎, 诗怀雅]
>>>

看起来这个reset函数没什么问题,然而,我们发现reset函数的效果却不理想,还是原来的卡池,并没有重置,其他卡池也无一例外。

这是因为什么呢?因为一开始six_stars指向initial_six,并不是将initial_six的内容复制下来,我们通过free_setter修改了six_stars,就等同于修改了initial_six

下面这个试验可以让你更懂这个原理:

>>> a=[]
>>> b=a
>>> b.append('1')
>>> a
['1']
>>>

这个问题同样发生在我们的程序里,好在这种问题有很多种解决办法:

  1. 将initial_six改为元组,让six_stars=list(initial_six),这样initial_six就不可变了,修改six_stars不会影响到initial_six
  2. 把initial_six的内容抄一遍,在代码中赋值给six_stars(不建议,这样你的代码太长了)
  3. 将initial_six的内容放进一个文件,six_stars从中读取(强烈建议,这样就容易对卡池进行更新,更新的时候只要发布卡池文件就好了,非常易于维护)

那么今天我们对标准寻访卡池和自定义寻访卡池的讲解就到这里结束,欢迎大家在下方留言,拓宽大家的视野。

本文为作者原创,未经作者允许,禁止转载。

----------------------END------------------------

Python实现《明日方舟》干员寻访模拟器第三期:原始但实用的卡池机制,不过要小心!相关推荐

  1. Python实现《明日方舟》干员寻访模拟器第二期:间接学习变化序列抽取目标事件和序列精确索引内容的经验

    上一期的文章我们实现了<明日方舟>干员寻访模拟器单次抽奖的功能,了解了random.choice这个函数的特点,通过原始奖池机制把等可能转化为不等可能,上一期我们还编写了一个便捷的不等可能 ...

  2. 明日方舟抽卡模拟器wiki_明日方舟wiki抽卡模拟器安卓手机下载-游戏大玩家

    明日方舟wiki抽卡模拟器这是一款由明日方舟衍生出来的全新游戏,这个游戏主要是针对游戏内的抽卡玩法而独自打造的一个模拟器,明日方舟的玩家可能会觉得游戏内的卡片资源非常的少,没有关系,在这款全新的模拟器 ...

  3. 基于python的明日方舟自动刷取理智的脚本(一)—— 初始化环境

    写在前面的话 身为一名计算机专业的懒鬼和明日方舟三年老博士(悲,在学习编程之后便一直有想法做一个脚本来解放我的双手.奈何此前一直没有多余的时间,放了假也是泡在网吧里.这学期接触了pyautogui库后 ...

  4. 用python求期望_用Python计算明日方舟2021龙门幸运墙期望

    按照去年的惯例,方舟今年春节的时候也整了个红包盲盒. 比起去年简单粗暴的直接送,今年的盲盒实际上增加了两层隐性的保底机制:第一层是每天有两次机会而非一次,两次尝试取收益更高的结果:第二层是如果不幸成为 ...

  5. 从明日方舟入手数据统计--盒须图

    (封面源自必应,侵删)(多图预警) 由于此文的目的是数据统计学习而非游戏攻略且时间仓促,文中所用数据皆出自题主自己的明日方舟干员库,可能与实际数据有所出入.如需要权威数据请去明日方舟官方wiki 引子 ...

  6. 明日方舟抽卡模拟器wiki_明日方舟抽卡模拟器wiki

    明日方舟抽卡模拟器wikiapp是一款明日方舟抽卡模拟器,明日方舟抽卡模拟器wikiapp完全还原了游戏中的抽卡场景,带给玩家们一个真实的体验感,有十连抽和单抽大家可以根据自己的需求选择,没有任何的消 ...

  7. 明日方舟如何刷初始号,明日方舟如何刷初始号教程

    相信大部分玩过明日方舟的玩家朋友都知道明日方舟可以刷初始,新手玩家刷到一个好的初始号可以比较轻松的度过新手期. 以前都是建立游客号过新手教程后刷初始的,但是现在明日方舟识别手机设备ID,导致15天内只 ...

  8. 硬核塔防策略手游《明日方舟》体验分析

    游戏类型 <明日方舟>是一款硬核塔防类策略型游戏,以二次元养成为题材,在病毒感染的末日为世界观背景下,地下基建与地面的整合运动相互衔接,整体节奏紧张刺激,即使在基建的环境也是紧张生产,人员 ...

  9. 《明日方舟》游戏分析

    作者 游戏版本 角色等级 ox 0.7.28 48 游戏时长 通关主线 干员数量 36天 4-10 61 目录: 一.游戏系统架构 1.1 系统结构说明 1.1.1 Arknights游戏类型 1.1 ...

最新文章

  1. 学习angularjs时遇到 XX is not a function
  2. DI / CDI –基础
  3. Zookeeper分布式安装部署
  4. iOS推送群发的问题,PushSharp作者的这篇讲得最详细了
  5. 一个程序员的郁闷吐槽
  6. golang:cannot unmarshal number into Go value of type []json.RawMessage
  7. 压缩文件苹果手机怎么解压「iphone技巧」
  8. Turbo码相关学习
  9. 自考计算机00051笔记,自考00051 管理系统中计算机应用自考资料笔记自考小抄.doc...
  10. 基于Java语言实现全国交通咨询模拟
  11. Java 并发编程(一):简介
  12. 看了它--你也能轻松部署vue3组件库
  13. D. New Year and the Permutation Concatenation 题解翻译+思路解释(官方为主,我为补充)+普通人能看得懂的代码(我照着思路写的哈哈哈)
  14. 视频号9大变现模式,总有一个适合你
  15. K8s学习(15)---DashBoard
  16. 简单几步解决svchost占用内存过高问题
  17. Codeforces Round #702 (Div. 3)全部题解
  18. Finereport 9.0升级到10.0工具下载[9-10升级工具]
  19. 图像处理(数字图像处理)
  20. OpenLayers 调用 Tomcat 发布的栅格切片服务

热门文章

  1. 给你的视频配音,只需这3款免费软件工具(文字转语音)!
  2. 低压保护测控系统解决方案
  3. C++对象模型——对象的差异(第一章)
  4. 怎样删除word文档中页眉的横线
  5. matlab提取DNA序列
  6. AMF目前有两种版本,AMF0和AMF3
  7. 烧心吃什么马上能缓解11 oracle,烧心吃什么马上能缓解
  8. android进、线程间通信方式
  9. 内控安全软件测试经验
  10. 【软考中级】软件设计师笔记