🚧 WIP: better and more flexible tirages

This commit is contained in:
ddorn 2020-05-06 02:27:09 +02:00
parent 7261a52454
commit 5ed508be10
3 changed files with 509 additions and 273 deletions

264
src/base_tirage.py Normal file
View File

@ -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."""

View File

@ -3,9 +3,11 @@
import asyncio
import random
from collections import defaultdict, namedtuple
from dataclasses import dataclass
from functools import wraps
from io import StringIO
from pprint import pprint
from typing import Type, Dict
from typing import Type, Dict, Union, Optional, List
import discord
import yaml
@ -13,13 +15,14 @@ from discord.ext import commands
from discord.ext.commands import group, Cog, Context
from discord.utils import get
from src.base_tirage import BaseTirage
from src.constants import *
from src.core import CustomBot
from src.errors import TfjmError, UnwantedCommand
__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):
@ -29,54 +32,246 @@ def in_passage_order(teams, round=0):
Record = namedtuple("Record", ["name", "pb", "penalite"])
class Team(yaml.YAMLObject):
yaml_tag = "Team"
def delete_and_pm(f):
@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):
self.name = team_role.name
self.mention = team_role.mention
self.tirage_order = [None, None]
self.passage_order = [None, None]
msg = await f(self, *args, **kwargs)
if msg:
await self.ctx.author.send(f"Raison: {msg}")
self.accepted_problems = [None, None]
self.drawn_problem = None # Waiting to be accepted or refused
self.rejected = [set(), set()]
def __str__(self):
s = StringIO()
pprint(self.__dict__, stream=s)
s.seek(0)
return s.read()
def send_all(f):
@wraps(f)
async def wrapper(self, *args, **kwargs):
async for msg in f(self, *args, **kwargs):
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 = {
# "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],
def records(self, teams, rnd):
"""Get the strings needed for show the tirage in a list of Records"""
return [
Record(
team.name,
(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]}
- 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]}
"""
@delete_and_pm
async def warn_wrong_team(self, expected, got):
return "ce n'était pas à ton tour."
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):
@ -86,47 +281,14 @@ class Tirage(yaml.YAMLObject):
assert len(teams) in (3, 4)
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)
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):
if self.phase.finished():
next_class = await self.phase.next(ctx)
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)
else:
# Continue on the same round.
@ -163,70 +325,6 @@ class Tirage(yaml.YAMLObject):
if self.channel in tirages:
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):
if len(self.teams) == 3:
table = r"""
@ -261,51 +359,7 @@ class Tirage(yaml.YAMLObject):
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):
@ -358,11 +412,7 @@ class OrderPhase(Phase):
re_do.extend(teams)
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:
self.set_order_for(team, None)
# We need to do this phase again.
@ -462,32 +512,12 @@ class TiragePhase(Phase):
), "Choosing pb for a team that has a pb..."
if not team.drawn_problem:
if yes:
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 :) "
)
pass
else:
if yes:
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:
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)
await ctx.send(msg)
team.rejected[self.round].add(team.drawn_problem)
team.drawn_problem = None
@ -502,10 +532,6 @@ class TiragePhase(Phase):
i = (i + 1) % len(self.teams)
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:
return all(team.accepted_problems[self.round] for team in self.teams)
@ -558,61 +584,12 @@ class TiragePhase(Phase):
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"):
def __init__(self, bot):
self.bot: CustomBot = bot
# 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
# if the cog is reloaded turing a tirage.
from src.tfjm_discord_bot import tirages
@ -773,16 +750,6 @@ class TirageCog(Cog, name="Tirages"):
}
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)
await self.tirages[channel_id].phase.start(ctx)

View File

@ -4,6 +4,11 @@ import psutil
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):
"""Return whether the member has a role with this name."""