GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — develop (#307)
by
unknown
46s
created

DivisionService   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 87
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 87
rs 10
c 0
b 0
f 0
wmc 24
1
# coding=utf-8
2
import logging
3
from abc import ABCMeta, abstractmethod
4
from typing import List, Dict
5
6
logger = logging.getLogger(__name__)
7
8
9
class Division:
10
    def __init__(self, id: int, name: str, league: int, threshold: float):
11
        self.id = id
12
        self.name = name
13
        self.league = league
14
        self.threshold = threshold
15
16
17
class PlayerDivisionInfo:
18
    def __init__(self, user_id: int, current_league: int, current_score: float):
19
        self.user_id = user_id
20
        self.league = current_league
21
        self.score = current_score
22
23
    def is_in_inferior_league(self, compared_to: 'PlayerDivisionInfo') -> bool:
24
        return self.league < compared_to.league
25
26
    def is_in_superior_league(self, compared_to: 'PlayerDivisionInfo') -> bool:
27
        return self.league > compared_to.league
28
29
    def __str__(self):
30
        return "PlayerDivisionInfo(user_id=%s, league=%s, score=%s)" % (self.user_id, self.league, self.score)
31
32
33
class DivisionAccessor(metaclass=ABCMeta):
34
    """
35
    Interface for the persistance layer
36
    """
37
38
    @abstractmethod
39
    async def get_divisions(self) -> List['Division']:
40
        """
41
        :return list of divisions
42
        """
43
        pass  # pragma: no cover
44
45
    @abstractmethod
46
    async def get_player_infos(self, season: int) -> List['PlayerDivisionInfo']:
47
        """
48
        :param season: requested season for all player infos
49
        :return list of player infos for given season
50
        """
51
        pass  # pragma: no cover
52
53
    @abstractmethod
54
    async def add_player(self, season: int, player: 'PlayerDivisionInfo') -> None:
55
        """
56
        Add a new player to the persistance layer
57
        :param player: new player with zero score and initial league
58
        """
59
        pass  # pragma: no cover
60
61
    @abstractmethod
62
    async def update_player(self, season: int, player: 'PlayerDivisionInfo') -> None:
63
        """
64
        Update a player after a game (league, score, games)
65
        :param player: updated player
66
        """
67
        pass  # pragma: no cover
68
69
70
class DivisionService:
71
    """
72
    Division service calculates changes to the ladder leagues & divisions after each game
73
    """
74
75
    def __init__(self, accessor: 'DivisionAccessor', season: int):
76
        self._divisions = None
77
        self._players = None
78
        self.season = season
79
        self.accessor = accessor
80
81
    async def get_divisions(self):
82
        if self._divisions is None:
83
            self._divisions = await self.accessor.get_divisions()
84
85
        return self._divisions
86
87
    async def _ensure_players(self):
88
        if self._players is None:
89
            players_infos = await self.accessor.get_player_infos(self.season)
90
            self._players = dict()
91
92
            for info in players_infos:
93
                self._players[info.user_id] = info
94
95
    async def _get_players(self) -> Dict[int, 'PlayerDivisionInfo']:
96
        await self._ensure_players()
97
98
        return self._players
99
100
    async def get_player(self, user_id: int):
101
        return (await self._get_players())[user_id]
102
103
    async def add_player(self, player_id: int) -> None:
104
        await self._ensure_players()
105
106
        logger.info("Added new player %s to divisions", player_id)
107
        self._players[player_id] = PlayerDivisionInfo(player_id, 1, 0.0)
108
        await self.accessor.add_player(self.season, self._players[player_id])
109
110
    async def update_player_stats(self, player: PlayerDivisionInfo, new_score: float) -> None:
111
        logger.debug("Update score for %s to %s", player)
112
        player.score = new_score
113
        await self.accessor.update_player(self.season, player)
114
115
    async def promote_player(self, player):
116
        logger.info("%s got promoted to league %s", player, player.league + 1)
117
        player.score = 0.0
118
        player.league += 1
119
        await self.accessor.update_player(self.season, player)
120
121
    async def post_result(self, player_one: int, player_two: int, winning_slot: int) -> None:
122
        """
123
        Post a ladder game result to the division system
124
        :param player_one: FAF User ID of 1st player
125
        :param player_two: FAF User ID of 2nd player
126
        :param winning_slot: 0 for draw, 1 for 1st player, 2 for 2nd player
127
        """
128
        players = await self._get_players()
129
130
        if player_one not in players:
131
            await self.add_player(self.season, player_one)
132
133
        if player_two not in players:
134
            await self.add_player(self.season, player_two)
135
136
        if winning_slot == 0:
137
            logger.info("Game ended in a draw - no changes in score")
138
            await self.update_player_stats(self._players[player_one], players[player_one].score)
139
            await self.update_player_stats(self._players[player_two], players[player_two].score)
140
            return
141
142
        winner = players[player_one] if winning_slot == 1 else players[player_two]
143
        loser = players[player_two] if winning_slot == 1 else players[player_one]
144
145
        if winner.is_in_inferior_league(loser):
146
            gain = 1.5
147
            loss = 1.0
148
        elif winner.is_in_superior_league(loser):
149
            gain = 0.5
150
            loss = 0.5
151
        else:
152
            gain = 1.0
153
            loss = 0.5
154
155
        logger.info("%s won against %s - gain: %s - loss: %s", winner, loser, gain, loss)
156
157
        if winner.score + gain > await self.max_league_threshold(winner.league):
158
            await self.promote_player(winner)
159
        else:
160
            await self.update_player_stats(winner, winner.score + gain)
161
162
        await self.update_player_stats(loser, max(0.0, loser.score - loss))
163
164
    async def get_player_division(self, user_id: int) -> 'Division':
165
        player = await self.get_player(user_id)
166
        return await self.get_division(player.league, player.score)
167
168
    async def max_league_threshold(self, league: int) -> float:
169
        return max([x.threshold for x in await self.get_divisions() if x.league == league])
170
171
    async def get_division(self, league: int, score: float) -> Division:
172
        divisions_of_league = [x for x in await self.get_divisions() if x.league == league]
173
174
        for division in sorted(divisions_of_league, key=lambda d: d.threshold):
175
            if division.threshold > score:
176
                return division
177
178
        logger.error("league %s has no division for score %s", league, score)
179
        return None
180