♻️ factor tirage logic out
This commit is contained in:
parent
472cdfc251
commit
3618da9fb7
|
@ -0,0 +1,518 @@
|
||||||
|
#!/bin/python
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
from collections import defaultdict, namedtuple
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
import discord
|
||||||
|
import yaml
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
from discord.utils import get
|
||||||
|
|
||||||
|
from src.constants import *
|
||||||
|
from src.errors import TfjmError, UnwantedCommand
|
||||||
|
|
||||||
|
__all__ = ["Tirage"]
|
||||||
|
|
||||||
|
|
||||||
|
def in_passage_order(teams, round=0):
|
||||||
|
return sorted(teams, key=lambda team: team.passage_order[round] or 0, reverse=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Team(yaml.YAMLObject):
|
||||||
|
yaml_tag = "Team"
|
||||||
|
|
||||||
|
def __init__(self, ctx, name):
|
||||||
|
self.name = name
|
||||||
|
self.mention = get(ctx.guild.roles, name=name).mention
|
||||||
|
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 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):
|
||||||
|
return f"""{self.mention}:
|
||||||
|
- 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 Tirage(yaml.YAMLObject):
|
||||||
|
yaml_tag = "Tirage"
|
||||||
|
|
||||||
|
def __init__(self, ctx, channel, teams):
|
||||||
|
assert len(teams) in (3, 4)
|
||||||
|
|
||||||
|
self.channel: int = channel
|
||||||
|
self.teams = [Team(ctx, 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, random.choice(PROBLEMS))
|
||||||
|
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.
|
||||||
|
# If a Phase wants to change the round
|
||||||
|
# it needs to change its own round.
|
||||||
|
self.phase = next_class(self, self.phase.round)
|
||||||
|
await self.phase.start(ctx)
|
||||||
|
|
||||||
|
async def end(self, ctx):
|
||||||
|
if False:
|
||||||
|
# Allow everyone to send messages again
|
||||||
|
send = discord.PermissionOverwrite() # reset
|
||||||
|
await ctx.channel.edit(overwrites={ctx.guild.default_role: send})
|
||||||
|
|
||||||
|
tl = []
|
||||||
|
if TIRAGES_FILE.exists():
|
||||||
|
with open(TIRAGES_FILE) as f:
|
||||||
|
tl = list(yaml.load_all(f))
|
||||||
|
else:
|
||||||
|
TIRAGES_FILE.touch()
|
||||||
|
tl.append(self)
|
||||||
|
with open(TIRAGES_FILE, "w") as f:
|
||||||
|
yaml.dump_all(tl, f)
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
f"A tout moment, ce rapport peut être envoyé avec `!show {len(tl) - 1}`"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def show(self, ctx):
|
||||||
|
teams = ", ".join(team.mention for team in self.teams)
|
||||||
|
msg = f"Voici un résumé du tirage entre les équipes {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 |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
| {0.name} | Opp | Déf | | Rap |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
| {0.name} | Rap | Opp | Déf | |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
| {0.name} | | Rap | Opp | Déf |
|
||||||
|
+-----+---------+---------+---------+---------+
|
||||||
|
```"""
|
||||||
|
Record = namedtuple("Record", ["name", "pb", "penalite"])
|
||||||
|
|
||||||
|
for round in (0, 1):
|
||||||
|
records = [
|
||||||
|
Record(
|
||||||
|
team.name,
|
||||||
|
(team.accepted_problems[round] or "- None")[0],
|
||||||
|
f"k = {team.coeff(round)} ",
|
||||||
|
)
|
||||||
|
for team in in_passage_order(self.teams, round)
|
||||||
|
]
|
||||||
|
|
||||||
|
msg += f"\n\n**{ROUND_NAMES[round].capitalize()}**:\n"
|
||||||
|
msg += table.format(*records) + "\n"
|
||||||
|
for team in self.teams:
|
||||||
|
msg += team.details(round)
|
||||||
|
|
||||||
|
await ctx.send(msg)
|
||||||
|
|
||||||
|
|
||||||
|
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, problem):
|
||||||
|
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):
|
||||||
|
def __init__(self, tirage, round, name, order_name, reverse=False):
|
||||||
|
super().__init__(tirage, round)
|
||||||
|
self.name = name
|
||||||
|
self.reverse = reverse
|
||||||
|
self.order_name = order_name
|
||||||
|
|
||||||
|
def order_for(self, team):
|
||||||
|
return getattr(team, self.order_name)[self.round]
|
||||||
|
|
||||||
|
def set_order_for(self, team, order):
|
||||||
|
getattr(team, self.order_name)[self.round] = order
|
||||||
|
|
||||||
|
async def dice(self, ctx, author, dice):
|
||||||
|
team = self.team_for(author)
|
||||||
|
|
||||||
|
if self.order_for(team) is None:
|
||||||
|
self.set_order_for(team, dice)
|
||||||
|
await ctx.send(f"L'équipe {team.mention} a obtenu... **{dice}**")
|
||||||
|
else:
|
||||||
|
raise UnwantedCommand("tu as déjà lancé un dé !")
|
||||||
|
|
||||||
|
def finished(self) -> bool:
|
||||||
|
return all(self.order_for(team) is not None for team in self.teams)
|
||||||
|
|
||||||
|
async def next(self, ctx) -> "Type[Phase]":
|
||||||
|
orders = [self.order_for(team) for team in self.teams]
|
||||||
|
if len(set(orders)) == len(orders):
|
||||||
|
# All dice are different: good
|
||||||
|
self.teams.sort(key=self.order_for, reverse=self.reverse)
|
||||||
|
await ctx.send(
|
||||||
|
f"L'ordre {self.name} pour ce tour est donc :\n"
|
||||||
|
" - "
|
||||||
|
+ "\n - ".join(
|
||||||
|
f"{team.mention} ({self.order_for(team)})" for team in self.teams
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self.NEXT
|
||||||
|
else:
|
||||||
|
# Find dice that are the same
|
||||||
|
count = defaultdict(list)
|
||||||
|
for team in self.teams:
|
||||||
|
count[self.order_for(team)].append(team)
|
||||||
|
|
||||||
|
re_do = []
|
||||||
|
for teams in count.values():
|
||||||
|
if len(teams) > 1:
|
||||||
|
re_do.extend(teams)
|
||||||
|
|
||||||
|
teams_str = ", ".join(team.role.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.
|
||||||
|
return self.__class__
|
||||||
|
|
||||||
|
|
||||||
|
class TiragePhase(Phase):
|
||||||
|
"""The phase where captains accept or refuse random problems."""
|
||||||
|
|
||||||
|
def __init__(self, tirage, round=0):
|
||||||
|
"""
|
||||||
|
The main phase of the Tirage.
|
||||||
|
:param tirage: Backreference to the tirage
|
||||||
|
:param round: round number, 0 for the first round and 1 for the second
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(tirage, round)
|
||||||
|
self.turn = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_team(self):
|
||||||
|
return self.teams[self.turn]
|
||||||
|
|
||||||
|
def available(self, problem):
|
||||||
|
return all(team.accepted_problems[self.round] != problem for team in self.teams)
|
||||||
|
|
||||||
|
async def choose_problem(self, ctx: Context, author, problem):
|
||||||
|
team = self.current_team
|
||||||
|
if self.team_for(author) != team:
|
||||||
|
raise UnwantedCommand(
|
||||||
|
f"C'est à {team.mention} de choisir "
|
||||||
|
f"un problème, merci d'attendre :)"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
team.accepted_problems[self.round] is None
|
||||||
|
), "Choosing pb for a team that has a pb..."
|
||||||
|
|
||||||
|
if team.drawn_problem:
|
||||||
|
raise UnwantedCommand(
|
||||||
|
"Vous avez déjà tiré un problème, merci de l'accepter (`!yes`) "
|
||||||
|
"ou de le refuser (`!no)`."
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(f"{team.mention} a tiré **{problem}** !")
|
||||||
|
if not self.available(problem):
|
||||||
|
await ctx.send(
|
||||||
|
f"Malheureusement, **{problem}** à déjà été choisi, "
|
||||||
|
f"vous pouvez tirer un nouveau problème."
|
||||||
|
)
|
||||||
|
elif problem in team.accepted_problems:
|
||||||
|
await ctx.send(
|
||||||
|
f"{team.mention} à tiré **{problem}** mais "
|
||||||
|
f"l'a déjà présenté au premier tour. "
|
||||||
|
f"Vous pouvez directement piocher un autre problème (`!rp`)."
|
||||||
|
)
|
||||||
|
elif problem in team.rejected[self.round]:
|
||||||
|
team.drawn_problem = problem
|
||||||
|
await ctx.send(
|
||||||
|
f"Vous avez déjà refusé **{problem}**, "
|
||||||
|
f"vous pouvez le refuser à nouveau (`!refuse`) et "
|
||||||
|
f"tirer immédiatement un nouveau problème "
|
||||||
|
f"ou changer d'avis et l'accepter (`!accept`)."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
team.drawn_problem = problem
|
||||||
|
if len(team.rejected[self.round]) >= MAX_REFUSE:
|
||||||
|
await ctx.send(
|
||||||
|
f"Vous pouvez accepter ou refuser **{problem}** "
|
||||||
|
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 ctx.send(
|
||||||
|
f"Vous pouvez accepter (`!oui`) ou refuser (`!non`) **{problem}**. "
|
||||||
|
f"Il reste {MAX_REFUSE - len(team.rejected[self.round])} refus sans pénalité "
|
||||||
|
f"pour {team.mention}."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def accept(self, ctx: Context, author, yes):
|
||||||
|
team = self.current_team
|
||||||
|
|
||||||
|
if self.team_for(author) != team:
|
||||||
|
raise UnwantedCommand(
|
||||||
|
f"c'est à {team.mention} "
|
||||||
|
f"de choisir un problème, merci d'attendre :)"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
team.accepted_problems[self.round] is None
|
||||||
|
), "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 :) "
|
||||||
|
)
|
||||||
|
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.drawn_problem = None
|
||||||
|
|
||||||
|
# Next turn
|
||||||
|
if self.finished():
|
||||||
|
self.turn = None
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find next team that needs to draw.
|
||||||
|
i = (self.turn + 1) % len(self.teams)
|
||||||
|
while self.teams[i].accepted_problems[self.round]:
|
||||||
|
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)
|
||||||
|
|
||||||
|
async def start(self, ctx: Context):
|
||||||
|
# First sort teams according to the tirage_order
|
||||||
|
self.teams.sort(key=lambda team: team.tirage_order[self.round])
|
||||||
|
|
||||||
|
if self.round == 0:
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
await ctx.send("Passons au tirage des problèmes !")
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
await ctx.send(
|
||||||
|
f"Les {self.captain_mention(ctx)}s vont tirer des problèmes au "
|
||||||
|
f"hasard, avec `!random-problem` ou `!rp` pour ceux qui aiment "
|
||||||
|
f"les abbréviations."
|
||||||
|
)
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
await ctx.send(
|
||||||
|
"Ils pouront ensuite accepter ou refuser les problèmes avec "
|
||||||
|
"`!accept` ou `!refuse`."
|
||||||
|
)
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
await ctx.send(
|
||||||
|
f"Chaque équipe peut refuser jusqu'a {MAX_REFUSE} "
|
||||||
|
f"problèmes sans pénalité (voir §13 du règlement). "
|
||||||
|
f"Un problème déjà rejeté ne compte pas deux fois."
|
||||||
|
)
|
||||||
|
await ctx.send("Bonne chance à tous ! C'est parti...")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Second round
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
await ctx.send(
|
||||||
|
"Il reste juste le tirage du deuxième tour. Les règles sont les mêmes qu'avant "
|
||||||
|
"à la seule différence qu'une équipe ne peut pas tirer le problème "
|
||||||
|
"sur lequel elle est passée au premier tour."
|
||||||
|
)
|
||||||
|
|
||||||
|
await asyncio.sleep(1.5)
|
||||||
|
await ctx.send(
|
||||||
|
f"{self.current_team.mention} à toi l'honneur! "
|
||||||
|
f"Lance `!random-problem` quand tu veux."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def next(self, ctx: Context) -> "Type[Phase]":
|
||||||
|
if self.round == 0:
|
||||||
|
await ctx.send("Nous allons passer au deuxième tour")
|
||||||
|
self.round = 1
|
||||||
|
return TirageOrderPhase
|
||||||
|
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. "
|
||||||
|
)
|
|
@ -1,8 +1,5 @@
|
||||||
#!/bin/python
|
#!/bin/python
|
||||||
import asyncio
|
|
||||||
import random
|
import random
|
||||||
from collections import defaultdict, namedtuple
|
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import yaml
|
import yaml
|
||||||
|
@ -10,511 +7,9 @@ from discord.ext import commands
|
||||||
from discord.ext.commands import Context, group, Cog
|
from discord.ext.commands import Context, group, Cog
|
||||||
from discord.utils import get
|
from discord.utils import get
|
||||||
|
|
||||||
|
from src.cogs.tirage_logic import TiragePhase, Tirage
|
||||||
from src.constants import *
|
from src.constants import *
|
||||||
from src.errors import TfjmError, UnwantedCommand
|
from src.errors import TfjmError
|
||||||
|
|
||||||
|
|
||||||
def in_passage_order(teams, round=0):
|
|
||||||
return sorted(teams, key=lambda team: team.passage_order[round] or 0, reverse=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Team(yaml.YAMLObject):
|
|
||||||
yaml_tag = "Team"
|
|
||||||
|
|
||||||
def __init__(self, ctx, name):
|
|
||||||
self.name = name
|
|
||||||
self.mention = get(ctx.guild.roles, name=name).mention
|
|
||||||
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 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):
|
|
||||||
return f"""{self.mention}:
|
|
||||||
- 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 Tirage(yaml.YAMLObject):
|
|
||||||
yaml_tag = "Tirage"
|
|
||||||
|
|
||||||
def __init__(self, ctx, channel, teams):
|
|
||||||
assert len(teams) in (3, 4)
|
|
||||||
|
|
||||||
self.channel: int = channel
|
|
||||||
self.teams = [Team(ctx, 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) -> bool:
|
|
||||||
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, random.choice(PROBLEMS))
|
|
||||||
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.
|
|
||||||
# If a Phase wants to change the round
|
|
||||||
# it needs to change its own round.
|
|
||||||
self.phase = next_class(self, self.phase.round)
|
|
||||||
await self.phase.start(ctx)
|
|
||||||
|
|
||||||
async def end(self, ctx):
|
|
||||||
if False:
|
|
||||||
# Allow everyone to send messages again
|
|
||||||
send = discord.PermissionOverwrite() # reset
|
|
||||||
await ctx.channel.edit(overwrites={ctx.guild.default_role: send})
|
|
||||||
|
|
||||||
tl = []
|
|
||||||
if TIRAGES_FILE.exists():
|
|
||||||
with open(TIRAGES_FILE) as f:
|
|
||||||
tl = list(yaml.load_all(f))
|
|
||||||
else:
|
|
||||||
TIRAGES_FILE.touch()
|
|
||||||
tl.append(self)
|
|
||||||
with open(TIRAGES_FILE, "w") as f:
|
|
||||||
yaml.dump_all(tl, f)
|
|
||||||
|
|
||||||
await ctx.send(
|
|
||||||
f"A tout moment, ce rapport peut être envoyé avec `!show {len(tl) - 1}`"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def show(self, ctx):
|
|
||||||
teams = ", ".join(team.mention for team in self.teams)
|
|
||||||
msg = f"Voici un résumé du tirage entre les équipes {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 |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
| {0.name} | Opp | Déf | | Rap |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
| {0.name} | Rap | Opp | Déf | |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
| {0.name} | | Rap | Opp | Déf |
|
|
||||||
+-----+---------+---------+---------+---------+
|
|
||||||
```"""
|
|
||||||
Record = namedtuple("Record", ["name", "pb", "penalite"])
|
|
||||||
|
|
||||||
for round in (0, 1):
|
|
||||||
records = [
|
|
||||||
Record(
|
|
||||||
team.name,
|
|
||||||
(team.accepted_problems[round] or "- None")[0],
|
|
||||||
f"k = {team.coeff(round)} ",
|
|
||||||
)
|
|
||||||
for team in in_passage_order(self.teams, round)
|
|
||||||
]
|
|
||||||
|
|
||||||
msg += f"\n\n**{ROUND_NAMES[round].capitalize()}**:\n"
|
|
||||||
msg += table.format(*records) + "\n"
|
|
||||||
for team in self.teams:
|
|
||||||
msg += team.details(round)
|
|
||||||
|
|
||||||
await ctx.send(msg)
|
|
||||||
|
|
||||||
|
|
||||||
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, problem):
|
|
||||||
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):
|
|
||||||
def __init__(self, tirage, round, name, order_name, reverse=False):
|
|
||||||
super().__init__(tirage, round)
|
|
||||||
self.name = name
|
|
||||||
self.reverse = reverse
|
|
||||||
self.order_name = order_name
|
|
||||||
|
|
||||||
def order_for(self, team):
|
|
||||||
return getattr(team, self.order_name)[self.round]
|
|
||||||
|
|
||||||
def set_order_for(self, team, order):
|
|
||||||
getattr(team, self.order_name)[self.round] = order
|
|
||||||
|
|
||||||
async def dice(self, ctx, author, dice):
|
|
||||||
team = self.team_for(author)
|
|
||||||
|
|
||||||
if self.order_for(team) is None:
|
|
||||||
self.set_order_for(team, dice)
|
|
||||||
await ctx.send(f"L'équipe {team.mention} a obtenu... **{dice}**")
|
|
||||||
else:
|
|
||||||
raise UnwantedCommand("tu as déjà lancé un dé !")
|
|
||||||
|
|
||||||
def finished(self) -> bool:
|
|
||||||
return all(self.order_for(team) is not None for team in self.teams)
|
|
||||||
|
|
||||||
async def next(self, ctx) -> "Type[Phase]":
|
|
||||||
orders = [self.order_for(team) for team in self.teams]
|
|
||||||
if len(set(orders)) == len(orders):
|
|
||||||
# All dice are different: good
|
|
||||||
self.teams.sort(key=self.order_for, reverse=self.reverse)
|
|
||||||
await ctx.send(
|
|
||||||
f"L'ordre {self.name} pour ce tour est donc :\n"
|
|
||||||
" - "
|
|
||||||
+ "\n - ".join(
|
|
||||||
f"{team.mention} ({self.order_for(team)})" for team in self.teams
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return self.NEXT
|
|
||||||
else:
|
|
||||||
# Find dice that are the same
|
|
||||||
count = defaultdict(list)
|
|
||||||
for team in self.teams:
|
|
||||||
count[self.order_for(team)].append(team)
|
|
||||||
|
|
||||||
re_do = []
|
|
||||||
for teams in count.values():
|
|
||||||
if len(teams) > 1:
|
|
||||||
re_do.extend(teams)
|
|
||||||
|
|
||||||
teams_str = ", ".join(team.role.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.
|
|
||||||
return self.__class__
|
|
||||||
|
|
||||||
|
|
||||||
class TiragePhase(Phase):
|
|
||||||
"""The phase where captains accept or refuse random problems."""
|
|
||||||
|
|
||||||
def __init__(self, tirage, round=0):
|
|
||||||
"""
|
|
||||||
The main phase of the Tirage.
|
|
||||||
:param tirage: Backreference to the tirage
|
|
||||||
:param round: round number, 0 for the first round and 1 for the second
|
|
||||||
"""
|
|
||||||
|
|
||||||
super().__init__(tirage, round)
|
|
||||||
self.turn = 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_team(self):
|
|
||||||
return self.teams[self.turn]
|
|
||||||
|
|
||||||
def available(self, problem):
|
|
||||||
return all(team.accepted_problems[self.round] != problem for team in self.teams)
|
|
||||||
|
|
||||||
async def choose_problem(self, ctx: Context, author, problem):
|
|
||||||
team = self.current_team
|
|
||||||
if self.team_for(author) != team:
|
|
||||||
raise UnwantedCommand(
|
|
||||||
f"C'est à {team.mention} de choisir "
|
|
||||||
f"un problème, merci d'attendre :)"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert (
|
|
||||||
team.accepted_problems[self.round] is None
|
|
||||||
), "Choosing pb for a team that has a pb..."
|
|
||||||
|
|
||||||
if team.drawn_problem:
|
|
||||||
raise UnwantedCommand(
|
|
||||||
"Vous avez déjà tiré un problème, merci de l'accepter (`!yes`) "
|
|
||||||
"ou de le refuser (`!no)`."
|
|
||||||
)
|
|
||||||
|
|
||||||
await ctx.send(f"{team.mention} a tiré **{problem}** !")
|
|
||||||
if not self.available(problem):
|
|
||||||
await ctx.send(
|
|
||||||
f"Malheureusement, **{problem}** à déjà été choisi, "
|
|
||||||
f"vous pouvez tirer un nouveau problème."
|
|
||||||
)
|
|
||||||
elif problem in team.accepted_problems:
|
|
||||||
await ctx.send(
|
|
||||||
f"{team.mention} à tiré **{problem}** mais "
|
|
||||||
f"l'a déjà présenté au premier tour. "
|
|
||||||
f"Vous pouvez directement piocher un autre problème (`!rp`)."
|
|
||||||
)
|
|
||||||
elif problem in team.rejected[self.round]:
|
|
||||||
team.drawn_problem = problem
|
|
||||||
await ctx.send(
|
|
||||||
f"Vous avez déjà refusé **{problem}**, "
|
|
||||||
f"vous pouvez le refuser à nouveau (`!refuse`) et "
|
|
||||||
f"tirer immédiatement un nouveau problème "
|
|
||||||
f"ou changer d'avis et l'accepter (`!accept`)."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
team.drawn_problem = problem
|
|
||||||
if len(team.rejected[self.round]) >= MAX_REFUSE:
|
|
||||||
await ctx.send(
|
|
||||||
f"Vous pouvez accepter ou refuser **{problem}** "
|
|
||||||
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 ctx.send(
|
|
||||||
f"Vous pouvez accepter (`!oui`) ou refuser (`!non`) **{problem}**. "
|
|
||||||
f"Il reste {MAX_REFUSE - len(team.rejected[self.round])} refus sans pénalité "
|
|
||||||
f"pour {team.mention}."
|
|
||||||
)
|
|
||||||
|
|
||||||
async def accept(self, ctx: Context, author, yes):
|
|
||||||
team = self.current_team
|
|
||||||
|
|
||||||
if self.team_for(author) != team:
|
|
||||||
raise UnwantedCommand(
|
|
||||||
f"c'est à {team.mention} "
|
|
||||||
f"de choisir un problème, merci d'attendre :)"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert (
|
|
||||||
team.accepted_problems[self.round] is None
|
|
||||||
), "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 :) "
|
|
||||||
)
|
|
||||||
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.drawn_problem = None
|
|
||||||
|
|
||||||
# Next turn
|
|
||||||
if self.finished():
|
|
||||||
self.turn = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# Find next team that needs to draw.
|
|
||||||
i = (self.turn + 1) % len(self.teams)
|
|
||||||
while self.teams[i].accepted_problems[self.round]:
|
|
||||||
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)
|
|
||||||
|
|
||||||
async def start(self, ctx: Context):
|
|
||||||
# First sort teams according to the tirage_order
|
|
||||||
self.teams.sort(key=lambda team: team.tirage_order[self.round])
|
|
||||||
|
|
||||||
if self.round == 0:
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await ctx.send("Passons au tirage des problèmes !")
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await ctx.send(
|
|
||||||
f"Les {self.captain_mention(ctx)}s vont tirer des problèmes au "
|
|
||||||
f"hasard, avec `!random-problem` ou `!rp` pour ceux qui aiment "
|
|
||||||
f"les abbréviations."
|
|
||||||
)
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await ctx.send(
|
|
||||||
"Ils pouront ensuite accepter ou refuser les problèmes avec "
|
|
||||||
"`!accept` ou `!refuse`."
|
|
||||||
)
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await ctx.send(
|
|
||||||
f"Chaque équipe peut refuser jusqu'a {MAX_REFUSE} "
|
|
||||||
f"problèmes sans pénalité (voir §13 du règlement). "
|
|
||||||
f"Un problème déjà rejeté ne compte pas deux fois."
|
|
||||||
)
|
|
||||||
await ctx.send("Bonne chance à tous ! C'est parti...")
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Second round
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await ctx.send(
|
|
||||||
"Il reste juste le tirage du deuxième tour. Les règles sont les mêmes qu'avant "
|
|
||||||
"à la seule différence qu'une équipe ne peut pas tirer le problème "
|
|
||||||
"sur lequel elle est passée au premier tour."
|
|
||||||
)
|
|
||||||
|
|
||||||
await asyncio.sleep(1.5)
|
|
||||||
await ctx.send(
|
|
||||||
f"{self.current_team.mention} à toi l'honneur! "
|
|
||||||
f"Lance `!random-problem` quand tu veux."
|
|
||||||
)
|
|
||||||
|
|
||||||
async def next(self, ctx: Context) -> "Type[Phase]":
|
|
||||||
if self.round == 0:
|
|
||||||
await ctx.send("Nous allons passer au deuxième tour")
|
|
||||||
self.round = 1
|
|
||||||
return TirageOrderPhase
|
|
||||||
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"):
|
||||||
|
@ -610,9 +105,10 @@ class TirageCog(Cog, name="Tirages"):
|
||||||
"""
|
"""
|
||||||
Commence un tirage avec 3 ou 4 équipes.
|
Commence un tirage avec 3 ou 4 équipes.
|
||||||
|
|
||||||
Cette commande attend trois trigrammes d'équipes, par ex:
|
Cette commande attend des trigrames d'équipes.
|
||||||
|
|
||||||
!draw start AAA BBB CCC
|
Exemple:
|
||||||
|
`!draw start AAA BBB CCC`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
channel: discord.TextChannel = ctx.channel
|
channel: discord.TextChannel = ctx.channel
|
||||||
|
@ -696,11 +192,11 @@ class TirageCog(Cog, name="Tirages"):
|
||||||
@draw_group.command(name="show")
|
@draw_group.command(name="show")
|
||||||
async def show_cmd(self, ctx: Context, tirage_id: str):
|
async def show_cmd(self, ctx: Context, tirage_id: str):
|
||||||
"""
|
"""
|
||||||
Affiche le résumé d'un tirage
|
Affiche le résumé d'un tirage.
|
||||||
|
|
||||||
Les ID de tirages valides sont visibles avec
|
Exemples:
|
||||||
`!draw show all` et les details avec `!draw show 42`
|
`!draw show all` - Liste les ID possible
|
||||||
(si l'ID qui vous intéresse est 42).
|
`!draw show 42` - Affiche le tirage n°42
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not TIRAGES_FILE.exists():
|
if not TIRAGES_FILE.exists():
|
||||||
|
|
Loading…
Reference in New Issue