近期接手一个游戏项目,由于项目涉及得分计算,故研究了下ELO算法。关于ELO算法的程序实现,是少之又少,好不容易看到了一篇,最终不得不关闭该页面,故决定自己实现。
1. ELO算法数学实现介绍
其中表达式各字母含义:
EA:预期A玩家的胜负值
EB:预期B玩家的胜负值
RA:A玩家当前的积分
RB:B玩家当前的积分
SA:实际胜负值(A VS B),胜=1,平=0.5,负=0
SB:实际胜负值(B VS A),胜=1,平=0.5,负=0
K :常量系数
2. 程序分析
2.1 从数学的角度来看,EA + EB = 1,但实际在程序计算中,很难获取精确的EA或EB,可能导致EA + EB != 1,所以我们这里选择计算出EA,而EB由1 - EA获取,这样可以修正一定的数据误差。再者,我们知道,在ELO算法中,两个玩家的得失分总是相抵的,也就是说,两个玩家的总分是不变的(在积分不存在限制的情况下),这样我们只要得出一个玩家的本次所增加的积分(失败表示增加的积分为负),便可求出其对手的积分变化情况。
2.2 在现实的业务场景中,积分总是以整数方式存在着,所以我们对计算所得的积分进行处理,数字从浮点型变成整型一般方式有:四舍五入,去尾及其它方式,在我看来,玩家竞技对于胜利的一方,不论双方的积分相差较大或较小,能应该增加积分,基于此考虑,我选择偏离0取整(即正数向上取整,负数向下取整),以保证每次不为和局的竞技都会产生积分变动。
2.3 另一个在实现中遇到的问题,是由2.1使用的方式引起的,即可能产生由公式计算而来的EB和由1 - EA计算而来的EB之间存在错误(精度问题),这将导致出现相同数据计算结果不同的问题。举个例子,由参数(A的积分,B的积分,A胜B)和参数(B的积分,A的积分,B负A)计算出来的结果是不一样的,就比如说,由参数(A的积分,B的积分,A胜B)算出的EA是0.516,而由(B的积分,A的积分,B负A)算出来的EB是0.481,这是有可能的,因为精度及取舍问题产生,所以,我们必须解决这个结果不可重复的问题。解决方式至少有两种,第一次,我们统一由胜利的一方计算出变动积分,但这个时候还要另外处理和局的情况。所以我使用另一种方式,统一由积分高的一方计算出变动积分,以下是JAVA代码实现:
package com.wulizhou.util;import java.math.BigDecimal;import com.wulizhou.modules.ELOResult;public class ELOUtils {private final static BigDecimal DONE = new BigDecimal("1.0");private final static BigDecimal D400 = new BigDecimal("400.0");/*** 通过ELO算法计算竞技得分* @param ra 玩家A本轮竞技前得分* @param rb 玩家B本轮竞技前得分* @param sa A vs B 结果:<PRE>胜利 1<br>平 0.5<br/>失败 0</PRE>* @param k 极限值,代表理论上最多可以赢一个玩家的得分和失分* @param limit 是否开启下限为0限制* @return 本轮结束后A、B玩家得分*/public static ELOResult rating(int ra, int rb, float sa, int k, boolean limit){// 统一由分数高的一方计算出积分变动情况if(ra < rb){ELOResult result = rating(rb, ra, 1.0f-sa, k, limit);int temp = result.getRa();result.setRa(result.getRb());result.setRb(temp);return result;}BigDecimal ea = DONE.divide(DONE.add(new BigDecimal(Math.pow(10, new BigDecimal(rb - ra).divide(D400, 6, BigDecimal.ROUND_HALF_UP).doubleValue()))), 6, BigDecimal.ROUND_HALF_UP);double score = new BigDecimal(k).multiply(new BigDecimal(sa).subtract(ea)).doubleValue();// 为正数变动积分向上取整int scoreI = (int) Math.ceil(score);if(score < 0d){ // 为负数变动积分向下取整scoreI = (int) Math.floor(score);}ELOResult elo = new ELOResult();elo.setRa((ra + scoreI < 0 && limit) ? 0 : ra + scoreI);elo.setRb((rb - scoreI < 0 && limit) ? 0 : rb - scoreI);return elo;}}
以下是ELOResult类代码
package com.wulizhou.modules;public class ELOResult {/*** 玩家A本轮竞技后得分*/private int ra;/*** 玩家B本轮竞技后得分*/private int rb;public int getRa() {return ra;}public void setRa(int ra) {this.ra = ra;}public int getRb() {return rb;}public void setRb(int rb) {this.rb = rb;}}
本文主要描述ELO算法的JAVA实现,关于ELO算法所解决的问题及算法详情,请自行百度。
好了,今天的分享就到这里了,由于是个人经验总结,难免会出错,欢迎大家批评指正,万般感谢。















