1# -*- coding: UTF-8 -*-
2
3from pychess.Utils.const import WHITE, WHITEWON, BLACK, BLACKWON, DRAW
4
5
6def get_elo_rating_change(model, overridden_welo, overridden_belo):
7    """ http://www.fide.com/fide/handbook.html?id=197&view=article (§8.5, July 2017) """
8
9    def individual_elo_change(elo_player, elo_opponent, blitz):
10        result = {}
11
12        # Adaptation of the inbound parameters
13        pprov = '?' in elo_player
14        try:
15            pval = int(elo_player.replace("?", ""))
16        except ValueError:
17            pval = 0
18        try:
19            oval = int(elo_opponent.replace("?", ""))
20        except ValueError:
21            oval = 0
22        if pval == 0 or oval == 0:
23            return None
24
25        # Development coefficient - We ignore the age of the player and we assume that
26        # he is already rated. The provisional flag '?' just denotes that he has not
27        # played his first 30 games, but it may also denotes that he never had any
28        # ranking. The calculation being based on the current game only, we can't
29        # handle that second specific case
30        if blitz:
31            k = 20
32        else:
33            if pprov:
34                k = 40
35            else:
36                if pval >= 2400:
37                    k = 10
38                else:
39                    k = 20
40
41        # Probability of gain
42        d = pval - oval
43        d = max(-400, d)
44        d = min(400, d)
45        # The approximate formula should not be used : result["pd"] = 1.0/(1+10**(-d/400))
46        pd = [50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 53, 53,
47              53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55,
48              56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58,
49              58, 58, 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61,
50              61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 64,
51              64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66,
52              66, 66, 67, 67, 67, 67, 67, 67, 67, 67, 68, 68, 68, 68, 68, 68, 68, 68, 69, 69,
53              69, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71,
54              71, 71, 71, 72, 72, 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73, 73, 73,
55              74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, 75, 75, 75, 76, 76,
56              76, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 77, 77, 77, 78, 78, 78, 78,
57              78, 78, 78, 78, 78, 78, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 80, 80, 80, 80,
58              80, 80, 80, 80, 80, 80, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 82, 82, 82,
59              82, 82, 82, 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 84,
60              84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 85, 85, 85, 85, 85, 85, 85, 85, 85,
61              85, 85, 85, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 87, 87, 87, 87,
62              87, 87, 87, 87, 87, 87, 87, 87, 87, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88,
63              88, 88, 88, 88, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 90, 90,
64              90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, 91,
65              91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 92, 92, 92, 92, 92, 92, 92, 92,
66              92][abs(d)] / 100.0
67        result["pd"] = pd if pval >= oval else 1.0 - pd
68
69        # New difference in Elo for loss, draw, win
70        for score in [0, 1, 2]:
71            result["diff%d" % score] = round(k * ([0, 0.5, 1][score] - result["pd"]), 1)
72
73        # Result
74        return result
75
76    # Gathering of the data
77    welo = model.tags["WhiteElo"] if overridden_welo is None else overridden_welo
78    belo = model.tags["BlackElo"] if overridden_belo is None else overridden_belo
79    blitz = model.timemodel.isBlitzFide()
80
81    # Result
82    result = [None, None]
83    result[WHITE] = individual_elo_change(welo, belo, blitz)
84    result[BLACK] = individual_elo_change(belo, welo, blitz)
85    return None if result[WHITE] is None or result[BLACK] is None else result
86
87
88def get_elo_rating_change_str(model, player, overridden_welo, overridden_belo):
89    """ Determination of the ELO rating change """
90
91    erc = get_elo_rating_change(model, overridden_welo, overridden_belo)
92    if erc is None:
93        return ""
94    erc = erc[player]
95
96    # Status of the game
97    if (model.status == WHITEWON and player == WHITE) or (model.status == BLACKWON and player == BLACK):
98        d = 2
99    else:
100        if (model.status == WHITEWON and player == BLACK) or (model.status == BLACKWON and player == WHITE):
101            d = 0
102        else:
103            if model.status == DRAW:
104                d = 1
105            else:
106                return "%.0f%%, %.1f / %.1f / %.1f" % (100 * erc["pd"], erc["diff0"], erc["diff1"], erc["diff2"])
107
108    # Result
109    return "%s%.1f" % ("+" if erc["diff%d" % d] > 0 else "", erc["diff%d" % d])
110
111
112def get_elo_rating_change_pgn(model, player):
113    # One move must occur to validate the rating
114    if model.ply == 0:
115        return ""
116
117    # Retrieval of the statistics for the player
118    data = get_elo_rating_change(model, None, None)
119    if data is None:
120        return ""
121    data = data[player]
122
123    # Status of the game
124    if (model.status == WHITEWON and player == WHITE) or (model.status == BLACKWON and player == BLACK):
125        d = 2
126    else:
127        if (model.status == WHITEWON and player == BLACK) or (model.status == BLACKWON and player == WHITE):
128            d = 0
129        else:
130            if model.status == DRAW:
131                d = 1
132            else:
133                return ""
134
135    # Result is rounded to the nearest integer
136    r = int(round(data["diff%s" % d], 0))
137    return "+%d" % r if r > 0 else str(r)
138