diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2023-02-19 12:47:44 +0100 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2023-02-19 12:47:44 +0100 |
| commit | 99f7e18c12945e9fbe22524a84a85e5733898dd2 (patch) | |
| tree | 9915227803f007bd4886ce2e997959e4cd30516c | |
| parent | af087e34e7a60e75db349aa9b3bb671b2aa70bfc (diff) | |
refactor: Split files
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | game.py | 14 | ||||
| -rw-r--r-- | ladder.py | 55 | ||||
| -rw-r--r-- | main.py | 212 | ||||
| -rw-r--r-- | player.py | 17 | ||||
| -rw-r--r-- | strategy.py | 36 |
6 files changed, 223 insertions, 114 deletions
@@ -1 +1,2 @@ -.ruff_cache/
\ No newline at end of file +.ruff_cache/ +__pycache__/
\ No newline at end of file @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from datetime import datetime + +from player import Player + + +@dataclass(frozen=True) +class Game: + played_at: datetime + scores: dict[Player, int] + + def __str__(self) -> str: + total_points = sum(self.scores.values()) + return f"Game played at {self.played_at} ({total_points} total points)" diff --git a/ladder.py b/ladder.py new file mode 100644 index 0000000..b05714c --- /dev/null +++ b/ladder.py @@ -0,0 +1,55 @@ +from __future__ import annotations +from dataclasses import dataclass +from datetime import datetime +from game import Game + +from player import Player +from strategy import GameEloChangeStrategy + + +@dataclass(frozen=True) +class PlayerStatus: + player: Player + elo: int + total_games: int + + @staticmethod + def create(player: Player) -> PlayerStatus: + return PlayerStatus(player=player, elo=1000, total_games=0) + + def update_elo(self, elo_update: int) -> PlayerStatus: + return PlayerStatus( + player=self.player, + elo=self.elo + elo_update, + total_games=self.total_games + 1, + ) + + +class Ladder: + strategy: GameEloChangeStrategy + players: dict[Player, PlayerStatus] = { + player: PlayerStatus.create(player) for player in Player + } + + def current_elo(self) -> dict[Player, int]: + return { + player: player_status.elo for player, player_status in self.players.items() + } + + def proccess_game(self, game: Game) -> dict[Player, float]: + players_elo_updates = self.strategy.proccess_game(game, self.current_elo()) + + for player, elo_update in players_elo_updates.items(): + self.players[player] = self.players[player].update_elo(int(elo_update)) + + return players_elo_updates + + def print_current(self): + print(f"Ladder ({datetime.now().strftime('%d/%m/%Y %H:%M')}):") + print("=" * 30) + for rank, player in enumerate( + sorted(self.players.values(), key=lambda player: player.elo, reverse=True) + ): + print( + f"{rank+1}.\t{player.player.value:<20}: {player.elo:>4d} points, {player.total_games:>3d} games" + ) @@ -1,122 +1,58 @@ -from __future__ import annotations -from dataclasses import dataclass from datetime import datetime -from enum import Enum -from typing import Protocol - - -class Player(Enum): - Emma = "Emma" - Kim = "Kim" - Pierre = "Pierre" - Theo = "Théo" - Leila = "Leïla" - Martin = "Martin" - Clementine = "Clémentine" - Thomas = "Thomas" - Baptiste = "Baptiste" - Tanguy = "Tanguy" - Frost = "Frost" - Felix = "Félix" - Axel = "Axel" - - -@dataclass(frozen=True) -class PlayerStatus: - player: Player - elo: int - total_games: int - - @staticmethod - def create(player: Player) -> PlayerStatus: - return PlayerStatus(player=player, elo=1000, total_games=0) - - def update_elo(self, elo_update: int) -> PlayerStatus: - return PlayerStatus(player=self.player, elo=self.elo + elo_update, total_games=self.total_games + 1) - - -@dataclass(frozen=True) -class Game: - played_at: datetime - scores: dict[Player, int] - - def __str__(self) -> str: - total_points = sum(self.scores.values()) - return f"Game played at {self.played_at} ({total_points} total points)" - - -class GameEloChangeStrategy(Protocol): - def proccess_game(self, game: Game, current_elo: dict[Player, int]) -> dict[Player, float]: - ... - -class SimpleEloChangeStrategy(GameEloChangeStrategy): - def proccess_game(self, game: Game, current_elo: dict[Player, int]) -> dict[Player, float]: - players_elo_updates = {player: 0.0 for player in game.scores} - - for player_1, score_1 in game.scores.items(): - for player_2, score_2 in game.scores.items(): - if player_1 != player_2: - expected_score = 1 / (1 + 10 ** ((current_elo[player_2] - current_elo[player_1]) / D)) - result = ( - 1.0 if score_1 > score_2 else 0.0 if score_1 < score_2 else 0.5 - ) - players_elo_updates[player_1] += K * (result - expected_score) - - return players_elo_updates - - -class Ladder: - strategy: GameEloChangeStrategy - players: dict[Player, PlayerStatus] = {player: PlayerStatus.create(player) for player in Player} - - def current_elo(self) -> dict[Player, int]: - return {player: player_status.elo for player, player_status in self.players.items()} - - def proccess_game(self, game: Game) -> dict[Player, float]: - players_elo_updates = self.strategy.proccess_game(game, self.current_elo()) - - for player, elo_update in players_elo_updates.items(): - self.players[player] = self.players[player].update_elo(int(elo_update)) - - return players_elo_updates - - def print_current(self): - print(f"Ladder ({datetime.now().strftime('%d/%m/%Y %H:%M')}):") - print("=" * 30) - for rank, player in enumerate( - sorted(self.players.values(), key=lambda player: player.elo, reverse=True) - ): - print( - f"{rank+1}.\t{player.player.value:<20}: {player.elo:>4d} points, {player.total_games:>3d} games" - ) - +from game import Game +from ladder import Ladder +from player import Player +from strategy import SimpleEloChangeStrategy ladder = Ladder() ladder.strategy = SimpleEloChangeStrategy() -D = 400 -K = 32 - - - games: list[Game] = [ Game( datetime(2022, 11, 11, 16, 30), - {Player.Theo: 53, Player.Leila: 32, Player.Clementine: 53, Player.Emma: 48, Player.Kim: 50, Player.Martin: 51}, + { + Player.Theo: 53, + Player.Leila: 32, + Player.Clementine: 53, + Player.Emma: 48, + Player.Kim: 50, + Player.Martin: 51, + }, ), Game( datetime(2022, 11, 11, 17, 0), - {Player.Emma: 53, Player.Kim: 57, Player.Pierre: 48, Player.Theo: 44, Player.Leila: 32, Player.Martin: 57}, + { + Player.Emma: 53, + Player.Kim: 57, + Player.Pierre: 48, + Player.Theo: 44, + Player.Leila: 32, + Player.Martin: 57, + }, ), Game( datetime(2022, 11, 11, 18, 45), - {Player.Thomas: 58, Player.Baptiste: 45, Player.Pierre: 54, Player.Theo: 65, Player.Emma: 42, Player.Martin: 53}, + { + Player.Thomas: 58, + Player.Baptiste: 45, + Player.Pierre: 54, + Player.Theo: 65, + Player.Emma: 42, + Player.Martin: 53, + }, ), Game( datetime(2022, 11, 11, 21, 0), - {Player.Kim: 50, Player.Theo: 50, Player.Emma: 53, Player.Clementine: 55, Player.Pierre: 46, Player.Baptiste: 54}, + { + Player.Kim: 50, + Player.Theo: 50, + Player.Emma: 53, + Player.Clementine: 55, + Player.Pierre: 46, + Player.Baptiste: 54, + }, ), Game( datetime(2022, 11, 11, 21, 30), @@ -131,29 +67,58 @@ games: list[Game] = [ }, ), Game( - datetime(2022, 11, 11, 22, 0), {Player.Clementine: 47, Player.Pierre: 46, Player.Theo: 57, Player.Emma: 42} + datetime(2022, 11, 11, 22, 0), + {Player.Clementine: 47, Player.Pierre: 46, Player.Theo: 57, Player.Emma: 42}, ), Game( - datetime(2022, 11, 11, 22, 32), {Player.Clementine: 43, Player.Pierre: 51, Player.Theo: 40, Player.Emma: 39} + datetime(2022, 11, 11, 22, 32), + {Player.Clementine: 43, Player.Pierre: 51, Player.Theo: 40, Player.Emma: 39}, ), Game( datetime(2022, 11, 12, 12, 48), - {Player.Pierre: 58, Player.Theo: 52, Player.Emma: 59, Player.Thomas: 50, Player.Baptiste: 52}, + { + Player.Pierre: 58, + Player.Theo: 52, + Player.Emma: 59, + Player.Thomas: 50, + Player.Baptiste: 52, + }, + ), + Game( + datetime(2022, 11, 12, 13, 18), + {Player.Emma: 41, Player.Theo: 53, Player.Pierre: 44}, + ), + Game( + datetime(2022, 11, 12, 13, 47), + {Player.Emma: 65, Player.Thomas: 59, Player.Pierre: 54}, ), - Game(datetime(2022, 11, 12, 13, 18), {Player.Emma: 41, Player.Theo: 53, Player.Pierre: 44}), - Game(datetime(2022, 11, 12, 13, 47), {Player.Emma: 65, Player.Thomas: 59, Player.Pierre: 54}), Game( datetime(2022, 11, 12, 14, 15), {Player.Pierre: 57, Player.Emma: 52, Player.Martin: 45, Player.Clementine: 47}, ), - Game(datetime(2022, 11, 12, 14, 47), {Player.Pierre: 51, Player.Emma: 47, Player.Clementine: 56}), + Game( + datetime(2022, 11, 12, 14, 47), + {Player.Pierre: 51, Player.Emma: 47, Player.Clementine: 56}, + ), Game( datetime(2022, 11, 12, 19, 6), - {Player.Clementine: 43, Player.Kim: 47, Player.Theo: 13, Player.Pierre: 47, Player.Emma: 62}, + { + Player.Clementine: 43, + Player.Kim: 47, + Player.Theo: 13, + Player.Pierre: 47, + Player.Emma: 62, + }, ), Game( datetime(2022, 11, 13, 12, 31), - {Player.Theo: 43, Player.Thomas: 37, Player.Tanguy: 44, Player.Pierre: 47, Player.Emma: 61}, + { + Player.Theo: 43, + Player.Thomas: 37, + Player.Tanguy: 44, + Player.Pierre: 47, + Player.Emma: 61, + }, ), Game( datetime(2022, 11, 13, 12, 54), @@ -167,15 +132,34 @@ games: list[Game] = [ Player.Emma: 46, }, ), - Game(datetime(2022, 11, 13, 13, 25), {Player.Pierre: 52, Player.Emma: 68, Player.Clementine: 57}), + Game( + datetime(2022, 11, 13, 13, 25), + {Player.Pierre: 52, Player.Emma: 68, Player.Clementine: 57}, + ), Game( datetime(2023, 1, 2, 10, 0), - {Player.Felix: 34, Player.Frost: 64, Player.Thomas: 51, Player.Baptiste: 53, Player.Emma: 68}, + { + Player.Felix: 34, + Player.Frost: 64, + Player.Thomas: 51, + Player.Baptiste: 53, + Player.Emma: 68, + }, + ), + Game( + datetime(2023, 1, 2, 10, 30), + {Player.Felix: 64, Player.Frost: 55, Player.Thomas: 62, Player.Emma: 67}, ), - Game(datetime(2023, 1, 2, 10, 30), {Player.Felix: 64, Player.Frost: 55, Player.Thomas: 62, Player.Emma: 67}), Game( datetime(2023, 2, 19, 12, 0), - {Player.Thomas: 56, Player.Axel: 53, Player.Emma: 61, Player.Theo: 57, Player.Pierre: 55, Player.Martin: 36}, + { + Player.Thomas: 56, + Player.Axel: 53, + Player.Emma: 61, + Player.Theo: 57, + Player.Pierre: 55, + Player.Martin: 36, + }, ), ] @@ -185,6 +169,8 @@ for game in games: players_elo_updates = ladder.proccess_game(game) for (player, elo_update) in players_elo_updates.items(): - print(f"\t{player.value:<20}{int(elo_update):+}\t({ladder.players[player].elo:>4d})") + print( + f"\t{player.value:<20}{int(elo_update):+}\t({ladder.players[player].elo:>4d})" + ) ladder.print_current() diff --git a/player.py b/player.py new file mode 100644 index 0000000..961f51a --- /dev/null +++ b/player.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class Player(Enum): + Emma = "Emma" + Kim = "Kim" + Pierre = "Pierre" + Theo = "Théo" + Leila = "Leïla" + Martin = "Martin" + Clementine = "Clémentine" + Thomas = "Thomas" + Baptiste = "Baptiste" + Tanguy = "Tanguy" + Frost = "Frost" + Felix = "Félix" + Axel = "Axel" diff --git a/strategy.py b/strategy.py new file mode 100644 index 0000000..85ea3e0 --- /dev/null +++ b/strategy.py @@ -0,0 +1,36 @@ +from typing import Protocol + +from game import Game +from player import Player + + +class GameEloChangeStrategy(Protocol): + def proccess_game( + self, game: Game, current_elo: dict[Player, int] + ) -> dict[Player, float]: + ... + + +class SimpleEloChangeStrategy(GameEloChangeStrategy): + D = 400 + K = 32 + + def proccess_game( + self, game: Game, current_elo: dict[Player, int] + ) -> dict[Player, float]: + players_elo_updates = {player: 0.0 for player in game.scores} + + for player_1, score_1 in game.scores.items(): + for player_2, score_2 in game.scores.items(): + if player_1 != player_2: + expected_score = 1 / ( + 1 + + 10 + ** ((current_elo[player_2] - current_elo[player_1]) / self.D) + ) + result = ( + 1.0 if score_1 > score_2 else 0.0 if score_1 < score_2 else 0.5 + ) + players_elo_updates[player_1] += self.K * (result - expected_score) + + return players_elo_updates |
