aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/__init__.py0
-rw-r--r--src/game.py14
-rw-r--r--src/ladder.py56
-rw-r--r--src/player.py17
-rw-r--r--src/strategy.py40
5 files changed, 127 insertions, 0 deletions
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/__init__.py
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