"""Typing in RSTT
This modules defines different componnents types for the package. RSTT user level functions and class methods are typehecked.
It helps raising meaningfull errors at runtime, write understandable functions.
The module purpose is also to help develloper to extent and integrate their own component that fit in the RSTT framework.
When a type name clashes with an existing class, the type name is prefixed by a capital `S`.
For example: :class:`rstt.stypes.SPlayer` should only be used for typehint in functions signatures. :class:`rstt.player.player.Player` is a class representing competitors.
.. note::
RSTT relies on typeguard 3.0.0 and `Duck-Typing` for function signatures.
Which means that Protocol are defined by the existence of attributes/methods, and not their type signatures.
"""
from dataclasses import dataclass
from typing import Union, Any, Iterable, Protocol, runtime_checkable
# TODO: work on Scheduler typing and similarities/differences between 'Tournament' & 'MatchMaking'
[docs]
@runtime_checkable
class SPlayer(Protocol):
"""Player interface
Runtime_checkable Protocol representating the notion of a participant in a :class:`rstt.stypes.SMatch` and an element in a :class:`rstt.ranking.ranking.Ranking`.
Examples of SPlayer are implemented in the subpackage :class:`rstt.player`
"""
[docs]
def name(self) -> str:
"""return the name of the competitors. Assumed to be a unique identifier"""
[docs]
def level(self) -> float:
"""return the level (or strenght, skills) of the splayer"""
Score = list[float]
"""Outcome of match
A list of float value, each representing the result of a corresponding participant in the Match.
"""
[docs]
@runtime_checkable
class SMatch(Protocol):
"""Match interface
Runtime_checkable Protocol representing a confrontation between :class:`rstt.stypes.SPlayer`.
Examples of SMatch can be found in the :class:`rstt.game.match` module.
"""
[docs]
def players(self) -> list[SPlayer]:
"""getter for match participants
Returns
-------
List[SPlayer]
The list of SPlayer who participates in the match. The order of players matches the order of float value in the :func:`rstt.stypes.SMatch.scores`
"""
[docs]
def scores(self) -> Score:
"""getter for the match score
Returns
-------
Score
The outcome of the match. The order of players returned by :func:`rstt.stypes.SMatch.players` matches the order of float value in the returned Score.
"""
[docs]
@runtime_checkable
class Event(Protocol):
"""Event interface
Runtime_checkable Protocol for competitive events.
Events are automated game generators. Typical events are tournament inheriting from the :class:`rstt.scheduler.competition.Competition` class.
Examples are :class:`rstt.scheduler.tournament.knockout.SingleEliminationBracket` and :class:`rstt.scheduler.tournament.groups.RoundRobin`
"""
[docs]
def name(self) -> str:
"""getter for the name event
Name are assumed to be unique identifier
Returns
-------
str
The name of the event
"""
[docs]
def games(self, by_rounds: bool) -> Union[list[SMatch], list[list[SMatch]]]:
"""getter for SMatch
Parameters
----------
by_rounds : bool
a boolean value indicating if the return should be a List[Smatch] (false), or a List[List[SMatch]]
Returns
-------
Union[List[SMatch], List[List[SMatch]]]
All SMatch played during the event.
"""
[docs]
def standing(self) -> dict[SPlayer, int]:
"""getter for the event standing
A standing represent the result of the event (and not a given SMatch). The place 1 indicates the winner of the event.
Returns
-------
Dict[SPlayer, int]
A dictionary indicating the final placement of all SPlayer that participated in the Event.
"""
[docs]
def participants(self) -> list[SPlayer]:
"""getter for the involved competitiors
Returns
-------
list[SPlayer]
A list of SPlayer taking part in the compeittion, i.e. playing games.
"""
[docs]
@dataclass
class Achievement:
"""DataClass indicating player's result in event.
Achievements are object generated by :class:`rstt.scheduler.tournament.competition.Competition`.
It is collected by :class:`rstt.player.palyer.Player` to track their history in events.
.. note::
Since achievements are automaticaly stored by Player, there is no field indicating the 'who'
"""
event_name: str
"""The name of the event"""
place: int
"""The place of the player in the final standing of the event"""
prize: float
"""The earnings of the player during the event"""
# ??? categorie/label(s) (world event, domestic league, playoffs etc.)
# ??? event_type (SEB, Snake, MM etc)
# ??? Match type (duel, many-versus-many, other not yet implemented ...)
# ??? teammates
[docs]
@runtime_checkable
class Solver(Protocol):
"""Solver interface
Runtime_checkable Protocol responsible of SMatch outcome generation. For a :class:`rstt.game.match.Duel`.
An example is a deterministic Solver where the player with the highest level wins the duel :class:`rstt.solver.solvers.BetterWin`
"""
[docs]
def solve(self, match: SMatch, *agrs, **kwargs) -> None:
"""Method that decide an SMatch outcome.
This method has three responsabilities.
- First it should generate a suitable game result (Score).
- Assign the result to the game
- add the game to the history of the game participants that tracks their games.
.. note::
An SMatch can only be assigned an outcome once, but the Solver does not need to take any precaution in that regards.
It is the responsability of the Smatch.
The same can be said about player tracking their game history, a match can only be added once,
but it is the player responsability to enforce it.
Parameters
----------
match : SMatch
A game to generate an outcome for.
"""
# -------------------------- #
# --- Typing for Ranking --- #
# -------------------------- #
[docs]
@runtime_checkable
class Inference(Protocol):
"""Inference Interface
Runtime_checkable Protocol responsible to estimate the level of SPlayer. Inference is a core component of :class:`rstt.ranking.ranking.Ranking`.
Examples are :class:`rstt.ranking.inferer.Elo` or :class:`rstt.ranking.inferer.EventStanding`.
"""
# NOTE: name inspiration: https://en.wikipedia.org/wiki/Statistical_inference
[docs]
def rate(self, *args, **kwargs) -> Any:
"""A method to rate Splayer based on 'observation'
Different Inference can have different parameters and returns values/types. There is no general patterns that can be extracted.
However, the existence of this method has the benefit to sementicly fit other famous package in the field of ranking system in competition, such as
`Openskill <https://openskill.me/en/stable/>`_ and `TrueSkill <https://trueskill.org>`_.
.. note::
For devellopers: The type-signature of the rate method is a 'contract' with the other components of a Ranking instance, namely a :class:`rstt.stypes.Observer` instance.
For example if the ranking uses a :class:`rstt.ranking.observer.GameByGame` Observer, then the inferer.rate() method should accept a List[Rating] and either a Score or a List[int] as parameters.
The GameByGame expect a List[Rating] as return value. The type of Rating has to be compatible with the :class:`rstt.stypes.RatingSystem` of the Ranking.
Parameters:
-----------
Any
The input of the rate method is usually some sort of prior 'Rating' or collection of 'Rating' and an indication of why/how the ratings should change.
Returns
-------
Any
The return of the rate method is usually a sort of updated 'Rating' or collection of 'Rating'.
"""
[docs]
@runtime_checkable
class RatingSystem(Protocol):
"""RatingSystem interface
Runtime_checkable Protocol acting as a sort of Dict[SPlayer, Rating] where Rating can be of Any type. RatingSystem are componnent of :class:`rstt.ranking.ranking.Ranking`
and provde an 'ordinal()' method used by the ranking to sort automatically players (assign a rank).
An example is the :class:`rstt.ranking.datamodel.KeyModel` that acts as a defaultdictionary, ensuring that any SPlayer has at least a default rating such that it can be ranked in a Ranking.
"""
[docs]
def get(self, key: SPlayer) -> Any:
"""getter method for rating
Parameters
----------
key : SPlayer
A key to get the corresponding value (rating)
Returns
-------
Any
A corresponding value, meant has the player's rating
"""
[docs]
def set(self, key: SPlayer, rating: Any) -> None:
"""setter method for rating
Allows a user to tune the rating of a SPlayer at will
Parameters
----------
key : SPlayer
A key for which the associated value should change
rating : Any
The new value associated to the provided key
"""
[docs]
def ordinal(self, rating: Any) -> float:
"""Convert a value to a float
This methods is used to compare keys to each others based on their associated values.
Parameters
----------
rating : Any
A rating to convert
Returns
-------
float
A single floating value. :class:`rstt.ranking.ranking.Ranking` assumes that a higher returned value means 'better'.
"""
[docs]
def keys(self):
"""Dict like keys method
Returns
-------
view object
view on the RatingSystem entries
"""
def __delitem__(self, key: SPlayer):
"""delitem magic method
Parameters
----------
key : SPlayer
a player to remove from the RatingSystem
"""
[docs]
@runtime_checkable
class Observer(Protocol):
"""Obersever Interface
Runtime_checkable Protocol implementing a workfow triggered by :func:`rstt.ranking.ranking.Ranking.update`.
The observer component of a ranking is responsible to update ratings of SPlayer based on 'observation' passed to the update() function.
Examples are :class:`rstt.ranking.observer.GameByGame` and :class:`rstt.ranking.observer.BatchGame`.
It is possible to entirely change the behaviour of Ranking by simply changing the observer component.
Indeed both the `Elo <https://en.wikipedia.org/wiki/Elo_rating_system>`_ and `Glicko <https://en.wikipedia.org/wiki/Glicko_rating_system>`_ rating systems can be used either with the GameByGame or BatchGame observer.
Yet, for identical input, the resulting updated can be different.
.. note::
'Observation' is an abstract arbitrary notion, it does not even need to be something passed as a parameters to a :func:`rstt.ranking.ranking.Ranking.update` method.
For example the :class:`rstt.ranking.standard.BTRanking` rank players based on the returned value of their level() method, which could change silently during a simulation.
In this case the ranking.update() method is called without parameters.
"""
'''
# TODO:
- work on observer as a pipeline of transformation and operation
- common components are:
* transform/format the observation(s)
* QUERY the datamodel
* call the Inference
* POST updates in the datamodel
* ...
'''
[docs]
def handle_observations(self, infer: Inference, datamodel: RatingSystem, *args, **kwargs) -> None:
"""method that triggers a rating update routine
The method uses a RatingSystem to get prior rating of Player and store updated ratings.
It uses an Inference instance to compute the updated ratings, usually from the prior ratings and some additional parameters
Parameters
----------
infer : Inference
An inference object responsible to update ratings
datamodel : RatingSystem
A ratingSystem object storing rating compatible with the passed infer.
"""
# ---------------------------- #
# --- Typing for Shceduler --- #
# ---------------------------- #
[docs]
@runtime_checkable
class Shuffler(Protocol):
[docs]
def rearange(self, status: list[int]) -> list[int]:
"""Reorder elements in a 'meaningfull' fashion
Parameters
----------
status : List[int]
A list of integers [e0, e1, e2, ...]
Returns
-------
List[int]
A list of the same integers, in a different order.
"""
[docs]
@runtime_checkable
class Seeder(Protocol):
[docs]
def seed(self, players: list[SPlayer], initial_seeds: Iterable, results: Any, **kwargs) -> list[SPlayer]:
"""Reorder players in a 'meaningfull' fashion
Parameters
----------
players : List[Player]
A list of Player to order
initial_seeds : Iterable
An original ordering of the players
results : Any
Any data structure containing games (type ´Game´) related to the involved players
Returns
-------
List[Player]
A list of the same players, in a different order
"""
[docs]
@runtime_checkable
class Generator(Protocol):
[docs]
def generate(self, status: Union[list[int], list[SPlayer]]) -> Union[list[list[int]], list[list[SPlayer]]]:
"""Generate different ordering version of a given List
the different version produced are refered as 'options'
For short input, this function could just produce all possible orderings.
For long inout, this function could implement greedy strategy.
Thus the input is a list, and the initial ordering is meaningfull for the strategy
Parameters
----------
status : Union[List[int], List[Player]]
A list of element to compute options
Returns
-------
Union[list[list[int]], list[list[SPlayer]]]
the options, A list of different 'option' which are reordering of the given 'status'
Idealy the first option is a direct, and 'obvious' transformation of status
"""
[docs]
@runtime_checkable
class Evaluator(Protocol):
[docs]
def eval(self, options: list[list[SPlayer]], initial_seeds: Iterable, results: Any, **kwargs) -> list[list[SPlayer]]:
"""reorder options based on criteria
The options can be evaluated based on an history of Game(s), or Player(s) appreciation.
Usually the eval function does some 'sorting' operations
Parameters
----------
options : List[List[Player]]
Different ordering of players possibility to evaluate
initial_seeds : Iterable
An apriori 'preference' on the player.
results : Any
A collection of results indicating palyer's performaces so far.
Returns
-------
List[List[Player]]
A reordered version of the parameter 'options'.
"""