"""Module for Competiton that takes the form of 'groups'
Such tournament do not have a direct elimination process, but allows participants to keep playing even after loses.
Groups usually have tables that track points to determine each participant achievements.
"""
from typing import Dict
from rstt import BetterWin
from . import Competition
from rstt.ranking.ranking import Ranking
from rstt.stypes import Solver
from rstt.utils import utils as uu, matching as um, competition as uc
import numpy as np
import math
[docs]
class RoundRobin(Competition):
def __init__(self, name: str, seeding: Ranking, solver: Solver = BetterWin(),
cashprize: Dict[int, float] = {}):
"""Round-Robin Tournament
Implements the famous tournament system. The matching technique used to generate matches is
the 'circle' algorithm illustrated `here <https://en.wikipedia.org/wiki/Round-robin_tournament#/media/File:Round-robin_tournament_10teams_en.png>`_.
A simpl specification of the tournament reads like this:
- a total of n x (n-1) matche is played in n-1 rounds.
- each rounds every participants play exactly one opponent.
- every players faces each others exactly once
- standing is based on the player's matches scores.
"""
super().__init__(name, seeding, solver, cashprize)
self.table = None
self.future_rounds = []
# --- Override --- #
def _initialise(self):
self._init_table()
self._init_future_rounds()
def _end_of_stage(self):
return not self.future_rounds
def _update(self):
for game in self.played_matches[-1]:
p1, p2 = game.player1(), game.player2()
s1, s2 = self.seeding[[p1, p2]]
self.table[s1][s2] += game.score(p1)
self.table[s2][s1] += game.score(p2)
def _standing(self):
standing = {}
groups = []
scores = np.sum(self.table, axis=0)
values = np.unique(scores)
for value in values:
index = np.where(scores == value)[0].tolist()
groups.append(self.seeding[index])
top = 0
for group in groups:
top += len(group)
standing.update({player: top for player in group})
return standing
[docs]
def generate_games(self):
return self.next_round()
# --- init stuff --- #
def _init_table(self):
self.table = np.zeros(
shape=(len(self.participants()), len(self.participants())))
def _init_future_rounds(self):
self.future_rounds = um.ruban(
[p for p in self.seeding if p in self.participants()])
# --- round mechanisme --- #
[docs]
def next_round(self):
# FIXME: seems unecessary -> this code in generate_games(self)
games = uc.playersToDuel(self.future_rounds.pop(0))
return games
[docs]
class SwissRound(RoundRobin):
def __init__(self, name: str, seeding: Ranking, solver: Solver = BetterWin(),
cashprize: Dict[int, float] = {}):
"""Swiss Round
Also known as `Swiss System <https://en.wikipedia.org/wiki/Swiss-system_tournament>`_.
It is a variation of the Round-Robin system, that fixes some issues:
- ~ n X log2(n) matches played, which for large n (participants) is significantly faster than round-robin.
- each rounds every participants play exactly one opponent with the same score. Which creates more interesting and balance game overall.
- every players should face at most once other players (not always possible).
.. warning::
- Undefined behaviour when the number of registered player is not a power of 2.
- The current matching strategy (greedy) has some issues and may lead to errors, this has been observed for number of participants above 256.
"""
super().__init__(name, seeding, solver, cashprize)
def _init_future_rounds(self):
self.future_rounds = [[player for player in self.seeding]]
# --- Override --- #
def _end_of_stage(self):
return len(self.played_matches) == int(math.log(len(self.participants()), 2))
def _update(self):
super()._update()
# !!! not how _end_of_stage() is meant to be used.
if not self._end_of_stage():
self.make_groups()
[docs]
def next_round(self):
games = [uc.find_valid_draw(draws=self.draws(
group), games=self.games()) for group in self.future_rounds]
return uu.flatten(games)
# --- round mechanisme --- #
[docs]
def make_groups(self):
self.future_rounds = []
scores = np.sum(self.table, axis=1)
values = np.unique(scores)
for value in values:
# build a round
index = np.where(scores == value)[0].tolist()
players = self.seeding[index]
self.future_rounds.append(players)
[docs]
def draws(self, players):
# FIXME: explore other methods / make it tunable. It could result in bug
return [uc.playersToDuel(round) for round in um.ruban(players)]