summaryrefslogtreecommitdiff
path: root/src/match_up/controller.py
blob: a21fced5f5f00d0fec611de0f2ca0f725b691855 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from typing import Tuple
from operator import attrgetter
from dataclasses import dataclass, field
from match_up.utilities import sample_list, Timer
from match_up.model import (
    Team,
    Game,
    PlayerList,
    CourtList,
    MAX_LEVEL,
    PLAYER_PER_COURT,
)


def _create_placeholders(courts: dict, name: str) -> list[Game]:
    dummy_team = Team(player1=name, player2=name)
    games = [Game(court, dummy_team, dummy_team) for court in courts]
    return games


def _normalize_skill(players: dict, new_max: int) -> int:
    player_levels = []
    for _, player in players.items():
        skill_level = player.skill
        new_level = round((new_max - 1) * (skill_level / MAX_LEVEL)) + 1
        player_levels.append(new_level)
    return player_levels


def _create_groups_of_four_players(players: dict, new_max: int) -> list[dict]:
    player_levels = _normalize_skill(players, new_max)
    identifiers = sample_list([*players], player_levels)
    clumps = []
    for i in range(0, len(players), PLAYER_PER_COURT):
        clumps.append(identifiers[i : i + PLAYER_PER_COURT])
    combinations = [{id: players[id] for id in clump} for clump in clumps]
    return combinations


def _create_games(courts: dict, players: list[dict], levels: int):
    grouped_players = _create_groups_of_four_players(players, levels)
    games = []
    for court, players in zip(courts, grouped_players):
        ids = [*players]
        team1 = Team(player1=players[ids[0]].name, player2=players[ids[3]].name)
        team2 = Team(player1=players[ids[1]].name, player2=players[ids[2]].name)
        games.append(Game(court, team1, team2))
    return games


@dataclass
class RoundGenerator:
    courts: CourtList = field(default_factory=lambda: CourtList())
    players: PlayerList = field(default_factory=lambda: PlayerList())
    round: int = 0
    proposal: tuple = tuple()

    def __post_init__(self) -> None:
        self.timer = Timer()
        self.history = {key: 0 for key, _ in self.players.get_players().items()}
        self.games = self._generate_proposal(MAX_LEVEL)[0]

    def _separate_courts(self, courts: dict) -> Tuple[dict, dict]:
        possible_games = min(len(courts), self.players.get_possible_game_count())
        courts_as_list = list(courts.items())
        used_courts = dict(courts_as_list[:possible_games])
        unused_courts = dict(courts_as_list[possible_games:])
        return used_courts, unused_courts

    def _select_players(self, count: int) -> dict:
        active_players = self.players.get_potential_players()
        identifiers = [*active_players]
        play_count = [
            (
                self.history[player_id]
                if player_id < self.players.guest_id_start
                else self.round
            )
            for player_id in active_players
        ]
        ids = sample_list(identifiers, play_count, count)
        return {player_id: active_players[player_id] for player_id in ids}

    def _create_inactive_games(self) -> list[Game]:
        active_courts, reserved_courts = self.courts.separate_courts()
        reserved_games = _create_placeholders(reserved_courts, "RESERVED")
        _, unused_courts = self._separate_courts(active_courts)
        blank_games = _create_placeholders(unused_courts, "---")
        games = reserved_games + blank_games
        return games

    def _create_active_games(self, total_levels: int):
        active_courts, _ = self.courts.separate_courts()
        used_courts, _ = self._separate_courts(active_courts)
        required_player_count = len(used_courts) * PLAYER_PER_COURT
        selected_players = self._select_players(required_player_count)
        games = _create_games(used_courts, selected_players, total_levels)
        return games, selected_players

    def _generate_proposal(self, total_levels: int) -> Tuple[list[Game], dict]:
        inactive_games = self._create_inactive_games()
        active_games, selected_players = self._create_active_games(total_levels)
        games = active_games + inactive_games
        games.sort(key=attrgetter("court"))
        return games, selected_players

    def clear(self) -> list[Game]:
        self.proposal = tuple()
        return self.games

    def propose(self, levels: int) -> list[Game]:
        self.proposal = self._generate_proposal(levels)
        return self.proposal[0]

    def confirm(self) -> list[Game]:
        proposed_games, proposed_players = self.proposal
        if self.games == proposed_games or len(proposed_games) == 0:
            return self.games
        self.games = proposed_games
        for player_id in proposed_players:
            if player_id < self.players.guest_id_start:
                self.history[player_id] = self.history[player_id] + 1
        self.proposal = tuple()
        self.round += 1
        self.timer.start()
        return self.games