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代码实现:
import java.math.BigDecimal;
public class EloMatch {
public static void main(String[] args) {
System.out.println("我启动了");
int aScore = 100;
int bScore = 50;
float sa = 0;
int k = 16;
EloResult eloResult = EloUtils.rating(aScore, bScore, sa, k, false);
if (sa == 1) {
System.out.println("A 赢了比赛后得分:" + eloResult.getRa());
System.out.println("B 输了比赛后得分:" + eloResult.getRb());
} else if (sa == 0) {
System.out.println("A 输了比赛后得分:" + eloResult.getRa());
System.out.println("B 赢了比赛后得分:" + eloResult.getRb());
}
}
}
class EloUtils {
private final static BigDecimal DONE = new BigDecimal("1.0");
private final static BigDecimal D400 = new BigDecimal("400.0");
/*
* 通过ELO算法计算比赛得分
* ra 玩家A本轮比赛前得分
* rb 玩家B本轮比赛钱得分
* sa A VS B 结果:胜利 1,平 0.5,失败 0 sa代表A玩家胜利或失败结果,统一由分数高的一方计算出积分变动的情况,若分数相同选择其中一方计算
* k 极限值,代表理论上最多可以赢的分数和失去的分数
* 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;
}
}
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;
}
}