From 7200fcf3f4e4c22193b5b96452f9aa5131b1bc1c Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Sun, 19 Feb 2023 12:57:27 +0100 Subject: refactor: Move to src --- src/__init__.py | 0 src/game.py | 14 ++++++++++++++ src/ladder.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/player.py | 17 +++++++++++++++++ src/strategy.py | 40 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 src/__init__.py create mode 100644 src/game.py create mode 100644 src/ladder.py create mode 100644 src/player.py create mode 100644 src/strategy.py (limited to 'src') diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/game.py b/src/game.py new file mode 100644 index 0000000..0e8803c --- /dev/null +++ b/src/game.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from datetime import datetime + +from src.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/src/ladder.py b/src/ladder.py new file mode 100644 index 0000000..8603e9d --- /dev/null +++ b/src/ladder.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime + +from src.game import Game +from src.player import Player +from src.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", + ) diff --git a/src/player.py b/src/player.py new file mode 100644 index 0000000..961f51a --- /dev/null +++ b/src/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/src/strategy.py b/src/strategy.py new file mode 100644 index 0000000..6713299 --- /dev/null +++ b/src/strategy.py @@ -0,0 +1,40 @@ +from typing import Protocol + +from src.game import Game +from src.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 -- cgit v1.3