🚧 WIP: better and more flexible tirages
This commit is contained in:
parent
7261a52454
commit
5ed508be10
|
@ -0,0 +1,264 @@
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from io import StringIO
|
||||||
|
from typing import Type, Union, Dict, List
|
||||||
|
|
||||||
|
import discord
|
||||||
|
|
||||||
|
from src.constants import *
|
||||||
|
|
||||||
|
|
||||||
|
class Event(asyncio.Event):
|
||||||
|
def __init__(self, team: str, value: Union[bool, int, str]):
|
||||||
|
super(Event, self).__init__()
|
||||||
|
self.value = value
|
||||||
|
self.team = team
|
||||||
|
self.response = None
|
||||||
|
|
||||||
|
|
||||||
|
class Team:
|
||||||
|
yaml_tag = "Team"
|
||||||
|
|
||||||
|
def __init__(self, team_role):
|
||||||
|
self.name = team_role.name
|
||||||
|
self.mention = team_role.mention
|
||||||
|
|
||||||
|
self.accepted_problems = [None, None]
|
||||||
|
self.rejected = [set(), set()]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
s = StringIO()
|
||||||
|
pprint(self.__dict__, stream=s)
|
||||||
|
s.seek(0)
|
||||||
|
return s.read()
|
||||||
|
|
||||||
|
__repr__ = __str__
|
||||||
|
|
||||||
|
def coeff(self, round):
|
||||||
|
if len(self.rejected[round]) <= MAX_REFUSE:
|
||||||
|
return 2
|
||||||
|
else:
|
||||||
|
return 2 - 0.5 * (len(self.rejected[round]) - MAX_REFUSE)
|
||||||
|
|
||||||
|
def details(self, round):
|
||||||
|
|
||||||
|
info = {
|
||||||
|
# "Accepté": self.accepted_problems[round],
|
||||||
|
"Refusés": ", ".join(p[0] for p in self.rejected[round])
|
||||||
|
if self.rejected[round]
|
||||||
|
else "aucun",
|
||||||
|
"Coefficient": self.coeff(round),
|
||||||
|
# "Ordre passage": self.passage_order[round],
|
||||||
|
}
|
||||||
|
|
||||||
|
width = max(map(len, info))
|
||||||
|
|
||||||
|
return "\n".join(f"`{n.rjust(width)}`: {v}" for n, v in info.items())
|
||||||
|
|
||||||
|
return f""" - Accepté: {self.accepted_problems[round]}
|
||||||
|
- Refusés: {", ".join(p[0] for p in self.rejected[round]) if self.rejected[round] else "aucun"}
|
||||||
|
- Coefficient: {self.coeff(round)}
|
||||||
|
- Ordre au tirage: {self.tirage_order[round]}
|
||||||
|
- Ordre de passage: {self.passage_order[round]}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Poule:
|
||||||
|
def __init__(self, poule, rnd):
|
||||||
|
self.poule = poule
|
||||||
|
self.rnd = rnd
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.poule}{self.rnd + 1}"
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTirage:
|
||||||
|
def __init__(self, *teams: discord.Role, fmt=(3, 3)):
|
||||||
|
assert sum(fmt) == len(teams)
|
||||||
|
|
||||||
|
self.teams: Dict[str, Team] = {t.name: Team(t) for t in teams}
|
||||||
|
self.format = fmt
|
||||||
|
self.queue = asyncio.Queue()
|
||||||
|
self.poules: Dict[Poule, List[str]] = {}
|
||||||
|
"""A mapping between the poule name and the list of teams in this poule."""
|
||||||
|
|
||||||
|
async def event(self, event: Event):
|
||||||
|
event.set()
|
||||||
|
await self.queue.put(event)
|
||||||
|
await event.wait()
|
||||||
|
return event.response
|
||||||
|
|
||||||
|
async def dice(self, trigram):
|
||||||
|
return await self.event(Event(trigram, random.randint(1, 100)))
|
||||||
|
|
||||||
|
async def rproblem(self, trigram):
|
||||||
|
team = self.teams[trigram]
|
||||||
|
rnd = 0 if team.accepted_problems[0] is None else 1
|
||||||
|
for poule, teams in self.poules.items():
|
||||||
|
if trigram in teams and poule.rnd == rnd:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return await self.warn_wrong_team(None, trigram)
|
||||||
|
|
||||||
|
other_pbs = [self.teams[team].accepted_problems[rnd] for team in teams]
|
||||||
|
available = [
|
||||||
|
pb
|
||||||
|
for pb in PROBLEMS
|
||||||
|
if pb not in team.accepted_problems and pb not in other_pbs
|
||||||
|
]
|
||||||
|
return await self.event(Event(trigram, random.choice(available)))
|
||||||
|
|
||||||
|
async def next(self, typ, team=None):
|
||||||
|
while True:
|
||||||
|
event = await self.queue.get()
|
||||||
|
if team is not None and event.team != team:
|
||||||
|
await self.warn_wrong_team(team, event.team)
|
||||||
|
elif not isinstance(event.value, typ):
|
||||||
|
await self.warn_unwanted(typ, event.value)
|
||||||
|
else:
|
||||||
|
event.clear()
|
||||||
|
return event
|
||||||
|
event.clear()
|
||||||
|
|
||||||
|
async def run(self):
|
||||||
|
|
||||||
|
await self.info_start()
|
||||||
|
|
||||||
|
self.poules = await self.make_poules()
|
||||||
|
|
||||||
|
for poule in self.poules:
|
||||||
|
await self.draw_poule(poule)
|
||||||
|
|
||||||
|
await self.info_finish()
|
||||||
|
|
||||||
|
async def get_dices(self, teams):
|
||||||
|
dices = {t: None for t in teams}
|
||||||
|
collisions = list(teams)
|
||||||
|
while collisions:
|
||||||
|
|
||||||
|
for t in collisions:
|
||||||
|
dices[t] = None
|
||||||
|
collisions = []
|
||||||
|
|
||||||
|
while None in dices.values():
|
||||||
|
event = await self.next(int)
|
||||||
|
|
||||||
|
# TODO: avoid KeyError
|
||||||
|
if dices[event.team] is None:
|
||||||
|
dices[event.team] = event.value
|
||||||
|
else:
|
||||||
|
await self.warn_twice(int)
|
||||||
|
|
||||||
|
if collisions:
|
||||||
|
await self.warn_colisions(collisions)
|
||||||
|
return dices
|
||||||
|
|
||||||
|
async def make_poules(self):
|
||||||
|
poules = {}
|
||||||
|
for rnd in (0, 1):
|
||||||
|
await self.start_make_poule(rnd)
|
||||||
|
|
||||||
|
dices = await self.get_dices(self.teams)
|
||||||
|
sorted_teams = sorted(self.teams, key=lambda t: dices[t])
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
for i, qte in enumerate(self.format):
|
||||||
|
letter = chr(ord("A") + i)
|
||||||
|
poules[Poule(letter, rnd)] = sorted_teams[idx : idx + qte]
|
||||||
|
idx += qte
|
||||||
|
|
||||||
|
await self.annonce_poules(poules)
|
||||||
|
return poules
|
||||||
|
|
||||||
|
async def draw_poule(self, poule):
|
||||||
|
# Trigrams in draw order
|
||||||
|
trigrams = await self.draw_order(poule)
|
||||||
|
|
||||||
|
# Teams in draw order
|
||||||
|
teams = [self.teams[tri] for tri in trigrams]
|
||||||
|
current = 0
|
||||||
|
while not all(team.accepted_problems[poule.rnd] for team in teams):
|
||||||
|
team = teams[current]
|
||||||
|
if team.accepted_problems[poule.rnd] is not None:
|
||||||
|
# The team already accepted a problem
|
||||||
|
current += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Choose problem
|
||||||
|
await self.start_select_pb(team)
|
||||||
|
event = await self.next(str, team.name)
|
||||||
|
# TODO: Add check for already selected / taken by someone else
|
||||||
|
# This is not a bug for now, since it cannot happen yet
|
||||||
|
await self.info_draw_pb(team, event.value, rnd)
|
||||||
|
|
||||||
|
# Accept it
|
||||||
|
accept = await self.next(bool, team.name)
|
||||||
|
if accept:
|
||||||
|
team.accepted_problems[poule.rnd] = event.value
|
||||||
|
await self.info_accepted(team, event.value)
|
||||||
|
else:
|
||||||
|
await self.info_rejected(team, event.value, rnd=poule.rnd)
|
||||||
|
team.rejected[poule.rnd].add(event.value)
|
||||||
|
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
await self.annonce_poule(poule)
|
||||||
|
|
||||||
|
async def draw_order(self, poule):
|
||||||
|
await self.start_draw_order(poule)
|
||||||
|
|
||||||
|
teams = self.poules[poule]
|
||||||
|
dices = await self.get_dices(teams)
|
||||||
|
|
||||||
|
order = sorted(self.teams, key=lambda t: dices[t], reverse=True)
|
||||||
|
|
||||||
|
await self.annonce_draw_order(order)
|
||||||
|
return order
|
||||||
|
|
||||||
|
async def warn_unwanted(self, wanted: Type, got: Type):
|
||||||
|
"""Called when a event of an unwanted type occurs."""
|
||||||
|
|
||||||
|
async def warn_wrong_team(self, expected, got):
|
||||||
|
"""Called when a team that should not play now put an event"""
|
||||||
|
|
||||||
|
async def warn_colisions(self, collisions: List[str]):
|
||||||
|
"""Called when there are collisions in a dice tirage."""
|
||||||
|
|
||||||
|
async def warn_twice(self, typ: Type):
|
||||||
|
"""Called when an event appears once again and not wanted."""
|
||||||
|
|
||||||
|
async def start_make_poule(self, rnd):
|
||||||
|
"""Called when it starts drawing the poules for round `rnd`"""
|
||||||
|
|
||||||
|
async def start_draw_order(self, poule):
|
||||||
|
"""Called when we start to draw the order."""
|
||||||
|
|
||||||
|
async def start_select_pb(self, team):
|
||||||
|
"""Called when a team needs to select a problem."""
|
||||||
|
|
||||||
|
async def annonce_poules(self, poules):
|
||||||
|
"""Called when all poules are defined."""
|
||||||
|
|
||||||
|
async def annonce_draw_order(self, order):
|
||||||
|
"""Called when the drawing order is defined."""
|
||||||
|
|
||||||
|
async def annonce_poule(self, poule):
|
||||||
|
"""Called when the problems and order for a poule is known."""
|
||||||
|
|
||||||
|
async def info_start(self):
|
||||||
|
"""Called at the start of the tirage."""
|
||||||
|
|
||||||
|
async def info_finish(self):
|
||||||
|
"""Called when the tirage has ended."""
|
||||||
|
|
||||||
|
async def info_draw_pb(self, team, pb, rnd):
|
||||||
|
"""Called when a team draws a problem."""
|
||||||
|
|
||||||
|
async def info_accepted(self, team, pb):
|
||||||
|
"""Called when a team accepts a problem."""
|
||||||
|
|
||||||
|
async def info_rejected(self, team, pb, rnd):
|
||||||
|
"""Called when a team rejects a problem,
|
||||||
|
before it is added to the rejected set."""
|
|
@ -3,9 +3,11 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import random
|
import random
|
||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict, namedtuple
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import wraps
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from typing import Type, Dict
|
from typing import Type, Dict, Union, Optional, List
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import yaml
|
import yaml
|
||||||
|
@ -13,13 +15,14 @@ from discord.ext import commands
|
||||||
from discord.ext.commands import group, Cog, Context
|
from discord.ext.commands import group, Cog, Context
|
||||||
from discord.utils import get
|
from discord.utils import get
|
||||||
|
|
||||||
|
from src.base_tirage import BaseTirage
|
||||||
from src.constants import *
|
from src.constants import *
|
||||||
from src.core import CustomBot
|
from src.core import CustomBot
|
||||||
from src.errors import TfjmError, UnwantedCommand
|
from src.errors import TfjmError, UnwantedCommand
|
||||||
|
|
||||||
__all__ = ["Tirage", "TirageCog"]
|
__all__ = ["Tirage", "TirageCog"]
|
||||||
|
|
||||||
from src.utils import send_and_bin
|
from src.utils import send_and_bin, french_join
|
||||||
|
|
||||||
|
|
||||||
def in_passage_order(teams, round=0):
|
def in_passage_order(teams, round=0):
|
||||||
|
@ -29,54 +32,246 @@ def in_passage_order(teams, round=0):
|
||||||
Record = namedtuple("Record", ["name", "pb", "penalite"])
|
Record = namedtuple("Record", ["name", "pb", "penalite"])
|
||||||
|
|
||||||
|
|
||||||
class Team(yaml.YAMLObject):
|
def delete_and_pm(f):
|
||||||
yaml_tag = "Team"
|
@wraps(f)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
await self.ctx.message.delete()
|
||||||
|
await self.ctx.author.send(
|
||||||
|
"J'ai supprimé ton message:\n> "
|
||||||
|
+ self.ctx.message.clean_content
|
||||||
|
+ "\nC'est pas grave, c'est juste pour ne pas encombrer "
|
||||||
|
"le chat lors du tirage."
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, ctx, team_role):
|
msg = await f(self, *args, **kwargs)
|
||||||
self.name = team_role.name
|
if msg:
|
||||||
self.mention = team_role.mention
|
await self.ctx.author.send(f"Raison: {msg}")
|
||||||
self.tirage_order = [None, None]
|
|
||||||
self.passage_order = [None, None]
|
|
||||||
|
|
||||||
self.accepted_problems = [None, None]
|
|
||||||
self.drawn_problem = None # Waiting to be accepted or refused
|
|
||||||
self.rejected = [set(), set()]
|
|
||||||
|
|
||||||
def __str__(self):
|
def send_all(f):
|
||||||
s = StringIO()
|
@wraps(f)
|
||||||
pprint(self.__dict__, stream=s)
|
async def wrapper(self, *args, **kwargs):
|
||||||
s.seek(0)
|
async for msg in f(self, *args, **kwargs):
|
||||||
return s.read()
|
await self.ctx.send(msg)
|
||||||
|
|
||||||
__repr__ = __str__
|
return wrapper
|
||||||
|
|
||||||
def coeff(self, round):
|
|
||||||
if len(self.rejected[round]) <= MAX_REFUSE:
|
|
||||||
return 2
|
|
||||||
else:
|
|
||||||
return 2 - 0.5 * (len(self.rejected[round]) - MAX_REFUSE)
|
|
||||||
|
|
||||||
def details(self, round):
|
class DiscordTirage(BaseTirage):
|
||||||
|
def __init__(self, ctx, *teams, fmt):
|
||||||
|
super(DiscordTirage, self).__init__(*teams, fmt=fmt)
|
||||||
|
self.ctx = ctx
|
||||||
|
self.captain_mention = get(ctx.guild.roles, name=Role.CAPTAIN).mention
|
||||||
|
|
||||||
info = {
|
def records(self, teams, rnd):
|
||||||
# "Accepté": self.accepted_problems[round],
|
"""Get the strings needed for show the tirage in a list of Records"""
|
||||||
"Refusés": ", ".join(p[0] for p in self.rejected[round])
|
|
||||||
if self.rejected[round]
|
return [
|
||||||
else "aucun",
|
Record(
|
||||||
"Coefficient": self.coeff(round),
|
team.name,
|
||||||
# "Ordre passage": self.passage_order[round],
|
(team.accepted_problems[rnd] or "- None")[0],
|
||||||
|
f"k = {team.coeff(rnd)} ",
|
||||||
|
)
|
||||||
|
for team in teams
|
||||||
|
]
|
||||||
|
|
||||||
|
@delete_and_pm
|
||||||
|
async def warn_unwanted(self, wanted: Type, got: Type):
|
||||||
|
|
||||||
|
texts = {
|
||||||
|
(int, str): "Il faut tirer un problème avec `!rp` et pas un dé.",
|
||||||
|
(int, bool): "Il faut accepter `!oui` ou refuser `!non` "
|
||||||
|
"le problème d'abord.",
|
||||||
|
(str, int): "Tu dois lancer un dé (`!dice 100`), pas choisir un problème.",
|
||||||
|
(str, bool): "Il faut accepter `!oui` ou refuser `!non` le "
|
||||||
|
"problème avant d'en choisir un autre",
|
||||||
|
(bool, str): "Tu es bien optimiste pour vouloir accepter un problème "
|
||||||
|
"avant de l'avoir tiré !"
|
||||||
|
if got
|
||||||
|
else "Halte là ! Ce serait bien de tirer un problème d'abord... "
|
||||||
|
"et peut-être qu'il te plaira :) ",
|
||||||
|
(bool, int): "Il tirer un dé avec `!dice 100` d'abord.",
|
||||||
}
|
}
|
||||||
|
|
||||||
width = max(map(len, info))
|
reason = texts.get((type(got), wanted))
|
||||||
|
|
||||||
return "\n".join(f"`{n.rjust(width)}`: {v}" for n, v in info.items())
|
if reason is None:
|
||||||
|
print(f"Weird, arguments for warn_unwanted were {wanted} and {got}")
|
||||||
|
reason = "Je sais pas, le code ne devrait pas venir ici..."
|
||||||
|
return reason
|
||||||
|
|
||||||
return f""" - Accepté: {self.accepted_problems[round]}
|
@delete_and_pm
|
||||||
- Refusés: {", ".join(p[0] for p in self.rejected[round]) if self.rejected[round] else "aucun"}
|
async def warn_wrong_team(self, expected, got):
|
||||||
- Coefficient: {self.coeff(round)}
|
return "ce n'était pas à ton tour."
|
||||||
- Ordre au tirage: {self.tirage_order[round]}
|
|
||||||
- Ordre de passage: {self.passage_order[round]}
|
async def warn_colisions(self, collisions: List[str]):
|
||||||
"""
|
await self.ctx.send(
|
||||||
|
f"Les equipes {french_join(collisions)} ont fait le même résultat "
|
||||||
|
"et doivent relancer un dé. "
|
||||||
|
"Le nouveau lancer effacera l'ancien."
|
||||||
|
)
|
||||||
|
|
||||||
|
@delete_and_pm
|
||||||
|
async def warn_twice(self, typ: Type):
|
||||||
|
|
||||||
|
if typ == int:
|
||||||
|
return "Tu as déjà lancé un dé, pas besoin de le refaire ;)"
|
||||||
|
|
||||||
|
print("Weird, DiscordTirage.warn_twice was called with", typ)
|
||||||
|
return "Je sais pas, le code ne devrait pas venir ici..."
|
||||||
|
|
||||||
|
@send_all
|
||||||
|
async def start_make_poule(self, rnd):
|
||||||
|
if rnd == 0:
|
||||||
|
yield (
|
||||||
|
f"Les {self.captain_mention}s, vous pouvez désormais tous lancer un dé 100 "
|
||||||
|
"comme ceci : `!dice 100`. "
|
||||||
|
"Les poules et l'ordre de passage lors du premier tour sera l'ordre croissant des dés, "
|
||||||
|
"c'est-à-dire que le plus petit lancer sera le premier à passer dans la poule A."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
yield (
|
||||||
|
f"Les {self.captain_mention}s, vous pouvez à nouveau tous lancer un dé 100, "
|
||||||
|
f"afin de déterminer les poules du second tour."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def start_draw_order(self, poule):
|
||||||
|
print(poule)
|
||||||
|
|
||||||
|
async def start_select_pb(self, team):
|
||||||
|
await self.ctx.send(f"C'est au tour de {team.mention} de choisir un problème.")
|
||||||
|
|
||||||
|
async def annonce_poules(self, poules):
|
||||||
|
print(poules)
|
||||||
|
|
||||||
|
@send_all
|
||||||
|
async def annonce_draw_order(self, order):
|
||||||
|
order_str = "\n ".join(f"{i}) {tri}" for i, tri in enumerate(order))
|
||||||
|
yield "L'ordre de tirage des problèmes pour ce tour est donc: \n" + order_str
|
||||||
|
|
||||||
|
async def annonce_poule(self, poule):
|
||||||
|
teams = [self.teams[tri] for tri in self.poules[poule]]
|
||||||
|
|
||||||
|
if len(teams) == 3:
|
||||||
|
table = """```
|
||||||
|
+-----+---------+---------+---------+
|
||||||
|
| | Phase 1 | Phase 2 | Phase 3 |
|
||||||
|
| | Pb {0.pb} | Pb {1.pb} | Pb {2.pb} |
|
||||||
|
+-----+---------+---------+---------+
|
||||||
|
| {0.name} | Déf | Rap | Opp |
|
||||||
|
+-----+---------+---------+---------+
|
||||||
|
| {1.name} | Opp | Déf | Rap |
|
||||||
|
+-----+---------+---------+---------+
|
||||||
|
| {2.name} | Rap | Opp | Déf |
|
||||||
|
+-----+---------+---------+---------+
|
||||||
|
```"""
|
||||||
|
else:
|
||||||
|
table = """```
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
| | Phase 1 | Phase 2 | Phase 3 | Phase 4 |
|
||||||
|
| | Pb {0.pb} | Pb {1.pb} | Pb {2.pb} | Pb {3.pb} |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
| {0.name} | Déf | | Rap | Opp |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
| {1.name} | Opp | Déf | | Rap |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
| {2.name} | Rap | Opp | Déf | |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
| {3.name} | | Rap | Opp | Déf |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
```"""
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"Résumé du tirage entre {french_join([t.name for t in teams])}",
|
||||||
|
color=EMBED_COLOR,
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name=ROUND_NAMES[poule.rnd].capitalize(),
|
||||||
|
value=table.format(*self.records(teams, poule.rnd)),
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
for team in teams:
|
||||||
|
embed.add_field(
|
||||||
|
name=team.name + " - " + team.accepted_problems[poule.rnd],
|
||||||
|
value=team.details(poule.rnd),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.set_footer(
|
||||||
|
text="Ce tirage peut être affiché à tout moment avec `!draw show XXX`"
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@send_all
|
||||||
|
async def info_start(self):
|
||||||
|
yield (
|
||||||
|
"Nous allons commencer le tirage des problèmes. "
|
||||||
|
"Seuls les capitaines de chaque équipe peuvent désormais écrire ici. "
|
||||||
|
"Merci de d'envoyer seulement ce que est nécessaire et suffisant au "
|
||||||
|
"bon déroulement du tirage. Vous pouvez à tout moment poser toute question "
|
||||||
|
"si quelque chose n'est pas clair ou ne va pas."
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"Pour plus de détails sur le déroulement du tirgae au sort, le règlement "
|
||||||
|
"est accessible sur https://tfjm.org/reglement."
|
||||||
|
)
|
||||||
|
|
||||||
|
yield (
|
||||||
|
"Nous allons d'abord tirer les poules et l'ordre de passage dans chaque tour "
|
||||||
|
"avec toutes les équipes puis pour chaque poule de chaque tour, nous tirerons "
|
||||||
|
"l'ordre de tirage pour le tour et les problèmes."
|
||||||
|
)
|
||||||
|
|
||||||
|
@send_all
|
||||||
|
async def info_finish(self):
|
||||||
|
yield "Le tirage est fini, merci à tout le monde !"
|
||||||
|
yield (
|
||||||
|
"Vous pouvez désormais trouver les problèmes que vous devrez opposer ou rapporter "
|
||||||
|
"sur la page de votre équipe."
|
||||||
|
)
|
||||||
|
# TODO: Save it
|
||||||
|
# TODO: make them available with the api
|
||||||
|
|
||||||
|
async def info_draw_pb(self, team, pb, rnd):
|
||||||
|
if pb in team.rejected[rnd]:
|
||||||
|
await self.ctx.send(
|
||||||
|
f"Vous avez déjà refusé **{pb}**, "
|
||||||
|
f"vous pouvez le refuser à nouveau (`!non`) et "
|
||||||
|
f"tirer immédiatement un nouveau problème "
|
||||||
|
f"ou changer d'avis et l'accepter (`!oui`)."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if len(team.rejected[rnd]) >= MAX_REFUSE:
|
||||||
|
await self.ctx.send(
|
||||||
|
f"Vous pouvez accepter ou refuser **{pb}** "
|
||||||
|
f"mais si vous choisissez de le refuser, il y "
|
||||||
|
f"aura une pénalité de 0.5 sur le multiplicateur du "
|
||||||
|
f"défenseur."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.ctx.send(
|
||||||
|
f"Vous pouvez accepter (`!oui`) ou refuser (`!non`) **{pb}**. "
|
||||||
|
f"Il reste {MAX_REFUSE - len(team.rejected[rnd])} refus sans pénalité "
|
||||||
|
f"pour {team.mention}."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def info_accepted(self, team, pb):
|
||||||
|
await self.ctx.send(
|
||||||
|
f"L'équipe {team.mention} a accepté "
|
||||||
|
f"**{pb}** ! Les autres équipes "
|
||||||
|
f"ne peuvent plus l'accepter."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def info_rejected(self, team, pb, rnd):
|
||||||
|
msg = f"{team.mention} a refusé **{pb}** "
|
||||||
|
if pb in team.rejected[rnd]:
|
||||||
|
msg += "sans pénalité."
|
||||||
|
else:
|
||||||
|
msg += "!"
|
||||||
|
await self.ctx.send(msg)
|
||||||
|
|
||||||
|
|
||||||
class Tirage(yaml.YAMLObject):
|
class Tirage(yaml.YAMLObject):
|
||||||
|
@ -86,47 +281,14 @@ class Tirage(yaml.YAMLObject):
|
||||||
assert len(teams) in (3, 4)
|
assert len(teams) in (3, 4)
|
||||||
|
|
||||||
self.channel: int = channel
|
self.channel: int = channel
|
||||||
self.teams = [Team(ctx, team) for team in teams]
|
self.teams = [Team(team) for team in teams]
|
||||||
self.phase = TirageOrderPhase(self, round=0)
|
self.phase = TirageOrderPhase(self, round=0)
|
||||||
|
|
||||||
def team_for(self, author):
|
|
||||||
for team in self.teams:
|
|
||||||
if get(author.roles, name=team.name):
|
|
||||||
return team
|
|
||||||
|
|
||||||
# Should theoretically not happen
|
|
||||||
raise TfjmError(
|
|
||||||
"Tu n'es pas dans une des équipes qui font le tirage, "
|
|
||||||
"merci de ne pas intervenir."
|
|
||||||
)
|
|
||||||
|
|
||||||
async def dice(self, ctx, n):
|
|
||||||
if n != 100:
|
|
||||||
raise UnwantedCommand(
|
|
||||||
"C'est un dé à 100 faces qu'il faut tirer! (`!dice 100`)"
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.phase.dice(ctx, ctx.author, random.randint(1, n))
|
|
||||||
await self.update_phase(ctx)
|
|
||||||
|
|
||||||
async def choose_problem(self, ctx):
|
|
||||||
await self.phase.choose_problem(ctx, ctx.author)
|
|
||||||
await self.update_phase(ctx)
|
|
||||||
|
|
||||||
async def accept(self, ctx, yes):
|
|
||||||
await self.phase.accept(ctx, ctx.author, yes)
|
|
||||||
await self.update_phase(ctx)
|
|
||||||
|
|
||||||
async def update_phase(self, ctx):
|
async def update_phase(self, ctx):
|
||||||
if self.phase.finished():
|
if self.phase.finished():
|
||||||
next_class = await self.phase.next(ctx)
|
next_class = await self.phase.next(ctx)
|
||||||
|
|
||||||
if next_class is None:
|
if next_class is None:
|
||||||
self.phase = None
|
|
||||||
await ctx.send(
|
|
||||||
"Le tirage est fini ! Bonne chance à tous pour la suite !"
|
|
||||||
)
|
|
||||||
await self.show(ctx)
|
|
||||||
await self.end(ctx)
|
await self.end(ctx)
|
||||||
else:
|
else:
|
||||||
# Continue on the same round.
|
# Continue on the same round.
|
||||||
|
@ -163,70 +325,6 @@ class Tirage(yaml.YAMLObject):
|
||||||
if self.channel in tirages:
|
if self.channel in tirages:
|
||||||
del tirages[self.channel]
|
del tirages[self.channel]
|
||||||
|
|
||||||
def records(self, round):
|
|
||||||
"""Get the strings needed for show the tirage in a list of Records"""
|
|
||||||
|
|
||||||
return [
|
|
||||||
Record(
|
|
||||||
team.name,
|
|
||||||
(team.accepted_problems[round] or "- None")[0],
|
|
||||||
f"k = {team.coeff(round)} ",
|
|
||||||
)
|
|
||||||
for team in in_passage_order(self.teams, round)
|
|
||||||
]
|
|
||||||
|
|
||||||
async def show(self, ctx):
|
|
||||||
teams = ", ".join(team.name for team in self.teams)
|
|
||||||
|
|
||||||
if len(self.teams) == 3:
|
|
||||||
table = """```
|
|
||||||
+-----+---------+---------+---------+
|
|
||||||
| | Phase 1 | Phase 2 | Phase 3 |
|
|
||||||
| | Pb {0.pb} | Pb {1.pb} | Pb {2.pb} |
|
|
||||||
+-----+---------+---------+---------+
|
|
||||||
| {0.name} | Déf | Rap | Opp |
|
|
||||||
+-----+---------+---------+---------+
|
|
||||||
| {1.name} | Opp | Déf | Rap |
|
|
||||||
+-----+---------+---------+---------+
|
|
||||||
| {2.name} | Rap | Opp | Déf |
|
|
||||||
+-----+---------+---------+---------+
|
|
||||||
```"""
|
|
||||||
else:
|
|
||||||
table = """```
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
| | Phase 1 | Phase 2 | Phase 3 | Phase 4 |
|
|
||||||
| | Pb {0.pb} | Pb {1.pb} | Pb {2.pb} | Pb {3.pb} |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
| {0.name} | Déf | | Rap | Opp |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
| {1.name} | Opp | Déf | | Rap |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
| {2.name} | Rap | Opp | Déf | |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
| {3.name} | | Rap | Opp | Déf |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
```"""
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
|
||||||
title=f"Résumé du tirage entre {teams}", color=EMBED_COLOR
|
|
||||||
)
|
|
||||||
|
|
||||||
for r in (0, 1):
|
|
||||||
embed.add_field(
|
|
||||||
name=ROUND_NAMES[r].capitalize(),
|
|
||||||
value=table.format(*self.records(r)),
|
|
||||||
inline=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
for team in in_passage_order(self.teams, r):
|
|
||||||
embed.add_field(
|
|
||||||
name=team.name + " - " + team.accepted_problems[r],
|
|
||||||
value=team.details(r),
|
|
||||||
inline=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
async def show_tex(self, ctx):
|
async def show_tex(self, ctx):
|
||||||
if len(self.teams) == 3:
|
if len(self.teams) == 3:
|
||||||
table = r"""
|
table = r"""
|
||||||
|
@ -261,51 +359,7 @@ class Tirage(yaml.YAMLObject):
|
||||||
|
|
||||||
|
|
||||||
class Phase:
|
class Phase:
|
||||||
NEXT = None
|
...
|
||||||
|
|
||||||
def __init__(self, tirage, round=0):
|
|
||||||
"""
|
|
||||||
A Phase of the tirage.
|
|
||||||
|
|
||||||
:param tirage: Backreference to the tirage
|
|
||||||
:param round: round number, 0 for the first round and 1 for the second
|
|
||||||
"""
|
|
||||||
|
|
||||||
assert round in (0, 1)
|
|
||||||
self.round = round
|
|
||||||
self.tirage: Tirage = tirage
|
|
||||||
|
|
||||||
def team_for(self, author):
|
|
||||||
return self.tirage.team_for(author)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def teams(self):
|
|
||||||
return self.tirage.teams
|
|
||||||
|
|
||||||
@teams.setter
|
|
||||||
def teams(self, teams):
|
|
||||||
self.tirage.teams = teams
|
|
||||||
|
|
||||||
def captain_mention(self, ctx):
|
|
||||||
return get(ctx.guild.roles, name=Role.CAPTAIN).mention
|
|
||||||
|
|
||||||
async def dice(self, ctx: Context, author, dice):
|
|
||||||
raise UnwantedCommand()
|
|
||||||
|
|
||||||
async def choose_problem(self, ctx: Context, author):
|
|
||||||
raise UnwantedCommand()
|
|
||||||
|
|
||||||
async def accept(self, ctx: Context, author, yes):
|
|
||||||
raise UnwantedCommand()
|
|
||||||
|
|
||||||
def finished(self) -> bool:
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
async def start(self, ctx):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def next(self, ctx: Context) -> "Type[Phase]":
|
|
||||||
return self.NEXT
|
|
||||||
|
|
||||||
|
|
||||||
class OrderPhase(Phase):
|
class OrderPhase(Phase):
|
||||||
|
@ -358,11 +412,7 @@ class OrderPhase(Phase):
|
||||||
re_do.extend(teams)
|
re_do.extend(teams)
|
||||||
|
|
||||||
teams_str = ", ".join(team.mention for team in re_do)
|
teams_str = ", ".join(team.mention for team in re_do)
|
||||||
await ctx.send(
|
|
||||||
f"Les equipes {teams_str} ont fait le même résultat "
|
|
||||||
"et doivent relancer un dé. "
|
|
||||||
"Le nouveau lancer effacera l'ancien."
|
|
||||||
)
|
|
||||||
for team in re_do:
|
for team in re_do:
|
||||||
self.set_order_for(team, None)
|
self.set_order_for(team, None)
|
||||||
# We need to do this phase again.
|
# We need to do this phase again.
|
||||||
|
@ -462,32 +512,12 @@ class TiragePhase(Phase):
|
||||||
), "Choosing pb for a team that has a pb..."
|
), "Choosing pb for a team that has a pb..."
|
||||||
|
|
||||||
if not team.drawn_problem:
|
if not team.drawn_problem:
|
||||||
if yes:
|
pass
|
||||||
raise UnwantedCommand(
|
|
||||||
"Tu es bien optimiste pour vouloir accepter un problème "
|
|
||||||
"avant de l'avoir tiré !"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise UnwantedCommand(
|
|
||||||
"Halte là ! Ce serait bien de tirer un problème d'abord... "
|
|
||||||
"et peut-être qu'il te plaira :) "
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if yes:
|
if yes:
|
||||||
team.accepted_problems[self.round] = team.drawn_problem
|
team.accepted_problems[self.round] = team.drawn_problem
|
||||||
await ctx.send(
|
|
||||||
f"L'équipe {team.mention} a accepté "
|
|
||||||
f"**{team.accepted_problems[self.round]}** ! Les autres équipes "
|
|
||||||
f"ne peuvent plus l'accepter."
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
msg = f"{team.mention} a refusé **{team.drawn_problem}** "
|
|
||||||
if team.drawn_problem in team.rejected[self.round]:
|
|
||||||
msg += "sans pénalité."
|
|
||||||
else:
|
|
||||||
msg += "!"
|
|
||||||
team.rejected[self.round].add(team.drawn_problem)
|
team.rejected[self.round].add(team.drawn_problem)
|
||||||
await ctx.send(msg)
|
|
||||||
|
|
||||||
team.drawn_problem = None
|
team.drawn_problem = None
|
||||||
|
|
||||||
|
@ -502,10 +532,6 @@ class TiragePhase(Phase):
|
||||||
i = (i + 1) % len(self.teams)
|
i = (i + 1) % len(self.teams)
|
||||||
self.turn = i
|
self.turn = i
|
||||||
|
|
||||||
await ctx.send(
|
|
||||||
f"C'est au tour de {self.current_team.mention} de choisir un problème."
|
|
||||||
)
|
|
||||||
|
|
||||||
def finished(self) -> bool:
|
def finished(self) -> bool:
|
||||||
return all(team.accepted_problems[self.round] for team in self.teams)
|
return all(team.accepted_problems[self.round] for team in self.teams)
|
||||||
|
|
||||||
|
@ -558,61 +584,12 @@ class TiragePhase(Phase):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class PassageOrderPhase(OrderPhase):
|
|
||||||
"""The phase to determine the chicken's order."""
|
|
||||||
|
|
||||||
NEXT = TiragePhase
|
|
||||||
|
|
||||||
def __init__(self, tirage, round=0):
|
|
||||||
super().__init__(tirage, round, "de passage", "passage_order", True)
|
|
||||||
|
|
||||||
async def start(self, ctx):
|
|
||||||
await ctx.send(
|
|
||||||
"Nous allons maintenant tirer l'ordre de passage durant le tour. "
|
|
||||||
"L'ordre du tour sera dans l'ordre décroissant des lancers, "
|
|
||||||
"c'est-à-dire que l'équipe qui tire le plus grand nombre "
|
|
||||||
"présentera en premier."
|
|
||||||
)
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
|
|
||||||
await ctx.send(
|
|
||||||
f"Les {self.captain_mention(ctx)}s, vous pouvez lancer "
|
|
||||||
f"à nouveau un dé 100 (`!dice 100`)"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TirageOrderPhase(OrderPhase):
|
|
||||||
"""Phase to determine the tirage's order."""
|
|
||||||
|
|
||||||
NEXT = PassageOrderPhase
|
|
||||||
|
|
||||||
def __init__(self, tirage, round=0):
|
|
||||||
super().__init__(tirage, round, "des tirages", "tirage_order", False)
|
|
||||||
|
|
||||||
async def start(self, ctx):
|
|
||||||
|
|
||||||
await asyncio.sleep(
|
|
||||||
0.5
|
|
||||||
) # The bot is more human if it doesn't type at the speed of light
|
|
||||||
await ctx.send(
|
|
||||||
"Nous allons d'abord tirer au sort l'ordre de tirage des problèmes "
|
|
||||||
f"pour le {ROUND_NAMES[self.round]}, "
|
|
||||||
"puis l'ordre de passage lors de ce tour."
|
|
||||||
)
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await ctx.send(
|
|
||||||
f"Les {self.captain_mention(ctx)}s, vous pouvez désormais lancer un dé 100 "
|
|
||||||
"comme ceci `!dice 100`. "
|
|
||||||
"L'ordre des tirages suivants sera l'ordre croissant des lancers. "
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TirageCog(Cog, name="Tirages"):
|
class TirageCog(Cog, name="Tirages"):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot: CustomBot = bot
|
self.bot: CustomBot = bot
|
||||||
|
|
||||||
# We retrieve the global variable.
|
# We retrieve the global variable.
|
||||||
# We don't want tirages to be ust an attribute
|
# We don't want tirages to be just an attribute
|
||||||
# as we want them to outlive the Cog, for instance
|
# as we want them to outlive the Cog, for instance
|
||||||
# if the cog is reloaded turing a tirage.
|
# if the cog is reloaded turing a tirage.
|
||||||
from src.tfjm_discord_bot import tirages
|
from src.tfjm_discord_bot import tirages
|
||||||
|
@ -773,16 +750,6 @@ class TirageCog(Cog, name="Tirages"):
|
||||||
}
|
}
|
||||||
await channel.edit(overwrites=overwrites)
|
await channel.edit(overwrites=overwrites)
|
||||||
|
|
||||||
await ctx.send(
|
|
||||||
"Nous allons commencer le tirage du premier tour. "
|
|
||||||
"Seuls les capitaines de chaque équipe peuvent désormais écrire ici. "
|
|
||||||
"Merci de d'envoyer seulement ce que est nécessaire et suffisant au "
|
|
||||||
"bon déroulement du tournoi. Vous pouvez à tout moment poser toute question "
|
|
||||||
"si quelque chose n'est pas clair ou ne va pas. \n\n"
|
|
||||||
"Pour plus de détails sur le déroulement du tirgae au sort, le règlement "
|
|
||||||
"est accessible sur https://tfjm.org/reglement."
|
|
||||||
)
|
|
||||||
|
|
||||||
self.tirages[channel_id] = Tirage(ctx, channel_id, teams)
|
self.tirages[channel_id] = Tirage(ctx, channel_id, teams)
|
||||||
await self.tirages[channel_id].phase.start(ctx)
|
await self.tirages[channel_id].phase.start(ctx)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,11 @@ import psutil
|
||||||
from discord.ext.commands import Bot
|
from discord.ext.commands import Bot
|
||||||
|
|
||||||
|
|
||||||
|
def french_join(l):
|
||||||
|
start = ", ".join(l[:-1])
|
||||||
|
return f"{start} et {l[-1]}"
|
||||||
|
|
||||||
|
|
||||||
def has_role(member, role: str):
|
def has_role(member, role: str):
|
||||||
"""Return whether the member has a role with this name."""
|
"""Return whether the member has a role with this name."""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue