Rational LPL

Posted Phile-matology

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Rational LPL相关的知识,希望对你有一定的参考价值。

分享记录一下这两天做出来的一个小小项目,内容最开始发布于我的知乎。

为什么这么多回答,一个来实证的都没有呢?

分析来分析去,其实大家都有自己的考量,对公平的定义不一样,对优势和优势程度的理解不一样,那一个赛制是否有问题无论如何都是讨论不出来的。

我也思考过很久,刷了不少回答,总得不出个所以然。一方面我相信不是所有的新方案都像夏季赛的导播一样拉胯,但是另一方面作为躺进棺材的凰粉,双亚总是令我耿耿于怀。

作为一个计算机相关行业的学生,我希望能找到一个办法终结这个思考,通过重新建构双败赛的模型和通过大规模的模拟,重现整个季后赛的旅程,看看fpx遗憾的双亚究竟是小概率事件的发生,还是被赛制拉了后腿。

原理解释通俗,不包含任何公式和代码,不想看原理的可以直接翻到最后看图和结果,想看源码的在原理部分结尾,想自己动手的同学们移步我的github,菜鸡学生,列位轻喷~

一、开赛前

我们用0-1之间的数来描绘一支队伍的纸面实力吧。

那么一支进入季后赛的战队,至少该有着lpl平均线以上的纸面实力,从季后赛队伍数量占总数的比例,我们也基本可以验证这一点。我们把进入季后赛的最低标准定在0.5。

由于不想讨论哪支战队该作为那个天花板’1‘,我选择让1成为一支完美的战队,显然,lpl永远不会有一支完全完美的队伍,所以在我们生成战队实力时,指定一个变量’实力极差‘,来限定最好战队与最差战队之间的纸面实力差距。之后我们在0.5和0.5+实力极差之间生成一批随机数给每个参赛队,当然,常规赛排名越高的战队,将得到越高的实力评分。

二、一场BO5

每场BO5的胜者都是未知的。实力强劲的队伍不一定能获得最终的胜利。这才是电子竞技的魅力。赛前的战术准备,选手状态的浮动,比赛中途的调整,都会影响一支队伍发挥出来的实力。考虑如上因素,我们提出这样的模型:

  • 发挥实力=(纸面实力+状态浮动)*战术储备+小局调整
  • 状态浮动是一个-0.1~0.1的随机值,模拟选手的临场发挥
  • 战术储备是一个0.8~1.2的随机值,模拟战队的赛前准备,由于这两者性质的不同,对发挥实力的影响模式也理应是不同的。
  • 小局调整是一个-0.02~0.02之间的随机值,调整时间为每一局比赛之后,模拟更衣室里新的布置与选手在前几局或吸收经验或被打麻了的变化。

对战双方每局的实力都确定了,又如何判断每一小局的胜负?

我们考虑到,胜率应当只与实力差相关,并且当实力出现差距时,胜利的天平将会迅速倾斜,故而用指数型函数来拟合。取以下两数据点:

  • 差距为0时,胜率为50%
  • 当差距达到0.3时,两个队伍的差距已经足够巨大了,我们引用一个置信区间中经常出现的值——95%,来作为领先队伍此时的胜率。

这样我们得到了领先队伍的胜率,简单的,取0~1的随机值来模拟这次比赛,小于胜率值的即认为是领先的队伍胜利,反之则是落后队伍获胜。

赛后调整之后进入第二局,这样重复多次直至BO5分出胜负。

(打个广告,除了模拟赛制优劣以外,代码还提供了模拟一次属于自己的季后赛旅途的功能嗷~看着喜欢的队伍在自己的程序里披荆斩棘,其实也是一件挺浪漫的事。)

三、赛制

我模拟了两种赛制:

  • 传统的分区单败淘汰赛,1458/2367分组,简单易懂。
  • 现行的四强双败赛制,为了减少思维量,我没有按照赛事原理在代码中采用胜者组败者组的设定,而是直接还原了赛制的整个流程,虽然算是偷懒的hard code,但是在现行赛制下是没有问题的,只不过不能兼容其他队伍数量的双败赛罢了。

四、模拟

为了考虑到各种情况,我思考了很久应当如何呈现结果而不引起不必要的争议。后来我想,最众口难调的就是给各个战队的具体实力评分吧。一个队伍的过强过弱,整个季后赛队伍上下限的差距,都会很大程度上影响冠军的归属,因此,我选择用’实力极差‘变量作为自变量,观察在LPL从群雄林立到“凤一其随”(bushi)的变化过程中,常规赛冠军夺冠概率的变化。(显而易见的,常规赛冠军应当被认为是季后赛开打前公认的实力巅峰,而其夺冠概率应当可以衡量赛制的合理性)

为了消除“随机模拟”中单次结果的随机性,(譬如给出差距过大的不合理评分),我对每个赛制下进行的季后赛各模拟了一万次,并将LPL的实力极差从0.05~0.5移动,步长为0.01。也就是说,一共进行了900000次季后赛,几百万次BO5,几千万场小局,我的电脑足足算了快十分钟。(有代码经验的同学应当知道,对一个一百多行的小程序,这个运行时间意味着多高的时间复杂度)

在原理部分结束时,先按约定把代码贴上来:

\'\'\'
这是一次验证赛制合理性的简单尝试。
代码中保留了一些最终没有使用到的功能,由于时间原因,没有再单独开发接口来使用
更新rank列表以使用最新常规赛排名
修改Init函数中的部分注释以开启自定义队名模式(注意,这一功能要求每一次模拟都要输入一次,如果更改用于大规模模拟,请修改rank列表
print均用于单次模拟结果的展示,包括每一个BO5的每一小局,这可以让你开启自己版本的季后赛旅程,但他们的胜败是不能被操控的
大批量模拟是基于极差的变化,观察季后赛队伍水平差距对常规赛冠军夺冠几率带来的影响
特别的,对比大批量模拟被用于比较LPL新旧赛制的合理性
\'\'\'



import random
import math
import matplotlib.pyplot as plt

global Teamlist
Teamlist = []
rank = [\'fpx\',\'edg\',\'ra\',\'rng\',\'tes\',\'blg\',\'we\',\'lng\',\'sn\',\'omg\']       #这是默认的常规赛排名,数据来源:2021LPL夏季赛
def Init(format,varience):
    global flag
    global Teamlist
    global rank
    Teamlist=[]
    if Format == \'半区淘汰赛\':
        for i in range(8):
            Teamlist.append(["",random.uniform(0.5,0.5+varience)])
    elif Format == \'四强双败赛\':
        for i in range(10):
            Teamlist.append(["",random.uniform(0.5,0.5+varience)])
    else:
        print("暂不支持此赛制") 
    Teamlist.sort(key = lambda x:x[1],reverse=True)
    #if flag:
        #print("请按常规赛排名顺序输入队伍名称")                   如果要开启自定义队名模式,请取消该行和上一行的注释,并修改下一个循环中的输入方式
    for j in range(len(Teamlist)):
        #Teamlist[j][0]=input("第"+str(j+1)+"名:")
        Teamlist[j][0]=rank[j]
    if flag:
        print("战队原始实力榜:")
        for j in range(len(Teamlist)):
            print(Teamlist[j])
    
def BestOf5Games(teamx,teamy):
    global flag
    global Teamlist
    strenth1 = Teamlist[teamx][1]+random.uniform(-0.1,0.1)
    strenth2 = Teamlist[teamy][1]+random.uniform(-0.1,0.1)
    tactics1 = random.uniform(0.8,1.2)
    tactics2 = random.uniform(0.8,1.2)
    wins={teamx:0,teamy:0}
    if flag:
        print("Origin: "+Teamlist[teamx][0]+" : "+str(strenth1*tactics1)+", "+Teamlist[teamy][0]+" : "+str(strenth2*tactics2))
    while(wins[teamx] < 3 and wins[teamy] < 3):
        if strenth1*tactics1>=strenth2*tactics2:
            teamxwinrate=1.72*math.log(strenth1*tactics1-strenth2*tactics2+1,math.e)+0.5
        else:
            teamxwinrate=1-(1.72*math.log(strenth2*tactics2-strenth1*tactics1+1,math.e)+0.5)
        result = random.random()-teamxwinrate
        if result <= 0:
            wins[teamx]+=1
        else:
            wins[teamy]+=1
        if flag:
            print("Game"+str(wins[teamx]+wins[teamy])+": "+str(result))
        strenth1+=random.uniform(-0.02,0.02)
        strenth2+=random.uniform(-0.02,0.02)
        if flag:
            print("After Game"+str(wins[teamx]+wins[teamy])+": "+Teamlist[teamx][0]+" : "+str(strenth1*tactics1)+", "+Teamlist[teamy][0]+" : "+str(strenth2*tactics2))
    winner = max(wins,key=wins.get)
    loser = min(wins,key=wins.get)
    if flag:
        print(Teamlist[teamx][0]+" vs "+Teamlist[teamy][0]+" : "+Teamlist[winner][0]+" wins at 3 : "+str(wins[loser])+"\\n")
    return (winner,loser)

def SemifinalsForKnockout(Team1,Team2,Team3,Team4):
    return BestOf5Games(BestOf5Games(Team1,Team4)[0],BestOf5Games(Team2,Team3)[0])[0]

def SemifinalsForDoubledefeat(Team1,Team2,Team3,Team4,Team5):
    return (Team1,BestOf5Games(Team2,BestOf5Games(Team3,BestOf5Games(Team4,Team5)[0])[0])[0])


def Playoffs(Format,va):
    Init(Format,va/100)
    if Format == \'半区淘汰赛\':
        if flag:
            print("上半区赛况:")
        DivWinner1=SemifinalsForKnockout(0,3,4,7)
        if flag:
            print("下半区赛况:")
        DivWinner2=SemifinalsForKnockout(1,2,5,6)
        Champion=BestOf5Games(DivWinner1,DivWinner2)[0]
        if flag:
            print("总冠军队伍是:"+Teamlist[Champion][0])
    elif Format == \'四强双败赛\':
        if flag:
            print("上半区赛况:")
        DivWinners1=SemifinalsForDoubledefeat(0,3,4,7,8)
        if flag:
            print("下半区赛况:")
        DivWinners2=SemifinalsForDoubledefeat(1,2,5,6,9)
        if flag:
            print("双败赛赛况:")
        (win1,lose1)=BestOf5Games(DivWinners1[0],DivWinners1[1])
        (win2,lose2)=BestOf5Games(DivWinners2[0],DivWinners2[1])
        (winchampion,lose3)=BestOf5Games(win1,win2)
        lose4=BestOf5Games(lose1,lose2)[0]
        losechampion=BestOf5Games(lose3,lose4)[0]
        Champion=BestOf5Games(winchampion,losechampion)[0]
        if flag:
            print("总冠军队伍是:"+Teamlist[Champion][0])
    else:
        print("暂不支持此赛制")
        Champion=-1
    return Champion



#以下是函数入口
flag=0
Mode=input("请输入模式:单次模拟/大规模模拟/对比大规模模拟:")
if Mode == \'单次模拟\':
    Format=input("请输入你想模拟的赛制:半区淘汰赛/四强双败赛:")
    flag=1
    varience=eval(input("请输入队伍实力极差:"))
    Playoffs(Format,varience)
elif Mode == \'大规模模拟\':
    Format=input("请输入你想模拟的赛制:半区淘汰赛/四强双败赛:")
    times=eval(input("请输入实验次数:"))
    recordx = []
    recordy = []
    for varience in range(5,50,1):
        count = 0
        recordx.append(varience/100)
        for i in range(times):
            count+=(Playoffs(Format,varience)==0)
        recordy.append(count/times)
    plt.plot(recordx,recordy)
    plt.show()
elif Mode == \'对比大规模模拟\':
    times=eval(input("请输入实验次数:"))
    Format=\'半区淘汰赛\'
    recordx = []
    recordy = []
    for varience in range(5,50,1):
        count = 0
        recordx.append(varience/100)
        for i in range(times):
            count+=(Playoffs(Format,varience)==0)
        recordy.append(count/times)
    plt.plot(recordx,recordy,color=\'red\')
    Format=\'四强双败赛\'
    recordx = []
    recordy = []
    for varience in range(5,50,1):
        count = 0
        recordx.append(varience/100)
        for i in range(times):
            count+=(Playoffs(Format,varience)==0)
        recordy.append(count/times)
    plt.plot(recordx,recordy,color=\'blue\')
    plt.show()
else:
    print("暂不支持此模式")

 

五、结果

得到的是一张这样的图:

其中红线属于传统赛制,而蓝线是现在争议颇多的双败赛。可以看到,在队伍实力差距较小时,蓝线的表现显著的优于红线,也就是说,双败赛在大家实力接近时,对更强的战队确实是更有利的。

需要说明的是,横轴越靠左,队伍之间实力差距越大,常规赛冠军的捧杯可能性自然越高,故而图像呈上升性显示了这个模型的合理性。

但我们也要注意到,整个曲线代表的夺冠概率是很低的——就算真的有所谓的“凤一其随”,就算有支队伍像当年的EDG一样宰制着整个LPL,他们的夺冠概率也不过接近50%而已。就算我们把不幸双亚的FPX抬高到完美的地位,那么两次无冠的概率也有25%以上。

尽管我的模型显然不够精确,毕竟一支真的完美的队伍不仅仅是纸面实力强大,更是在我取了随机值的战术、状态、调整……等方面都有着拉满的实力,早已超出了我模型的约束。但是我们要明白,在模型的假设下,在现实的赛场上,双亚的FPX也好,MSI冠军归来却一轮游出局的RNG也好,接过RNG春季剧本从败者组浴火重生,再捧银龙杯的EDG也好,他们都是凡人。会有状态波动,战术失误,临场发挥,甚至就是因为一点点运气,就与最高的王座失之交臂。

但这不代表他们没有实力,也不代表有实力的队伍就该夺得冠军,我们提升自己,从不是为了预定未来,而是为了在未来来到时,能有更大的机会抓(na)住(hui)属于自己的一切。

回到正题,双败赛制究竟如何?作为实证者,我举双手双脚支持它,因为它告诉我,比起以往的赛制,它已经更合理了。尽管它可能还不够合理,BAN位优势也好,小局优势也罢,改进的空间我相信存在,但是给这个联赛一点时间和试错机会吧,即便它在电子竞技的世界里已经算不上个小孩,但是,真正的大师,永远应该怀着一颗学徒的心。选手如是,联赛亦然。

最后,恭喜EDG,FPX,RNG,LNG!!!LPL冲冲冲!!!

以上是关于Rational LPL的主要内容,如果未能解决你的问题,请参考以下文章

Sympy - 生成 C 代码。将 Rational 转换为浮点数

OO日报:Firefox风哥 LPL选手的才华超过我的预期

Firefox出任LPL梦之队教练 中单位置竞争激烈

PAT1081:Rational Sum

1088. Rational Arithmetic (20)——PAT (Advanced Level) Practise

1081. Rational Sum (20)模拟——PAT (Advanced Level) Practise