trirages in two parts for finale

This commit is contained in:
ddorn 2020-05-20 02:52:42 +02:00
parent 924abea6c8
commit 161478caf8
5 changed files with 180 additions and 36 deletions

View File

@ -15,6 +15,21 @@ import yaml
from src.constants import * from src.constants import *
def skip_if(check, default=None):
"""Decorator that skips running the function if the check is False, and returns the default."""
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
if check(*args, **kwargs):
return default
return f(*args, **kwargs)
return wrapper
return decorator
class Event(asyncio.Event): class Event(asyncio.Event):
def __init__(self, team: str, value: Union[bool, int, str]): def __init__(self, team: str, value: Union[bool, int, str]):
super(Event, self).__init__() super(Event, self).__init__()
@ -145,14 +160,16 @@ class BaseTirage(yaml.YAMLObject):
return event return event
event.clear() event.clear()
async def run(self): async def run(self, rounds=(0, 1)):
await self.info_start() await self.info_start()
self.poules = await self.make_poules() for i in rounds:
new_poules = await self.make_poules(i)
self.poules.update(new_poules)
for poule in self.poules: for poule in new_poules:
await self.draw_poule(poule) await self.draw_poule(poule)
await self.info_finish() await self.info_finish()
@ -181,19 +198,20 @@ class BaseTirage(yaml.YAMLObject):
return dices return dices
async def make_poules(self): async def make_poules(self, rnd):
"""Put teams in poules for a given round (0 or 1)."""
poules = {} poules = {}
for rnd in (0, 1): await self.start_make_poule(rnd)
await self.start_make_poule(rnd)
dices = await self.get_dices(self.teams) dices = await self.get_dices(self.teams)
sorted_teams = sorted(self.teams, key=lambda t: dices[t]) sorted_teams = sorted(self.teams, key=lambda t: dices[t])
idx = 0 idx = 0
for i, qte in enumerate(self.format): for i, qte in enumerate(self.format):
letter = chr(ord("A") + i) letter = chr(ord("A") + i)
poules[Poule(letter, rnd)] = sorted_teams[idx : idx + qte] poules[Poule(letter, rnd)] = sorted_teams[idx : idx + qte]
idx += qte idx += qte
await self.annonce_poules(poules) await self.annonce_poules(poules)
return poules return poules

View File

@ -2,6 +2,7 @@
import asyncio import asyncio
import random import random
import re
import sys import sys
import traceback import traceback
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
@ -25,8 +26,11 @@ from src.errors import TfjmError, UnwantedCommand
__all__ = ["TirageCog"] __all__ = ["TirageCog"]
from src.utils import send_and_bin, french_join from src.utils import send_and_bin, french_join, pprint_send, confirm
RE_DRAW_START = re.compile(
r"^((?P<fmt>\d(\+\d)*) )?(?P<teams>[A-Z]{3}(\s[A-Z]{3})+)((?P<finale>\s--finale)|(\s--continue[= ](?P<continue>\d+)))$"
)
Record = namedtuple("Record", ["name", "pb", "penalite"]) Record = namedtuple("Record", ["name", "pb", "penalite"])
@ -244,7 +248,9 @@ class DiscordTirage(BaseTirage):
@safe @safe
async def start_select_pb(self, team): async def start_select_pb(self, team):
await self.ctx.send(f"C'est au tour de {team.mention} de choisir un problème.") await self.ctx.send(
f"C'est au tour de {team.mention} de choisir un problème (`!rp`)."
)
@safe @safe
@send_all @send_all
@ -255,12 +261,10 @@ class DiscordTirage(BaseTirage):
second = "\n".join( second = "\n".join(
f"{p}: {french_join(t)}" for p, t in poules.items() if p.rnd == 1 f"{p}: {french_join(t)}" for p, t in poules.items() if p.rnd == 1
) )
yield ( if first:
f"Les poules sont donc, pour le premier tour :" yield (f"Les poules sont donc, pour le premier tour :" f"```{first}```\n")
f"```{first}```\n" if second:
f"Et pour le second tour :" yield (f"Pour le second tour les poules sont :" f"```{second}```")
f"```{second}```"
)
@safe @safe
@send_all @send_all
@ -578,17 +582,20 @@ class TirageCog(Cog, name="Tirages"):
await ctx.invoke(self.bot.get_command("help"), "draw") await ctx.invoke(self.bot.get_command("help"), "draw")
@draw_group.command( @draw_group.command(
name="start", usage="équipe1 équipe2 équipe3 (équipe4)", name="start", usage="FMT TRI1 TRI2... [--finale] [--continue=ID]",
) )
@commands.has_any_role(*Role.ORGAS) @commands.has_any_role(*Role.ORGAS)
async def start(self, ctx: Context, fmt, *teams: discord.Role): async def start(self, ctx: Context, *args):
""" """
(orga) Commence un tirage avec 3 ou 4 équipes. (orga) Commence un tirage avec 3 ou 4 équipes.
Cette commande attend des trigrames d'équipes. Cette commande attend des trigrames d'équipes.
Exemple: Exemple:
`!draw start AAA BBB CCC` `!draw start 5 AAA BBB CCC DDD EEE` - Tirage à une poule de 5 équipes
`!draw start 3+3 AAA BBB CCC DDD EEE FFF` - Deux poules de 3 équipes
`!draw start 3 AAA BBB CCC --finale` - Tirage seulement du premier tour
`!draw start AAA BBB CCC --continue=7` - Continue un tirage commencé avec `--finale`
""" """
channel: discord.TextChannel = ctx.channel channel: discord.TextChannel = ctx.channel
@ -599,22 +606,70 @@ class TirageCog(Cog, name="Tirages"):
"il est possible d'en commencer un autre sur une autre channel." "il est possible d'en commencer un autre sur une autre channel."
) )
try: query = " ".join(args)
fmt = list(map(int, fmt.split("+"))) match = re.match(RE_DRAW_START, query)
except ValueError:
raise TfjmError( if match is None:
"Le premier argument doit être le format du tournoi, " await ctx.send("La commande est mal formée.")
"par exemple `3+3` pour deux poules à trois équipes" return await ctx.invoke(self.bot.get_command("help"), "draw start")
teams = match["teams"].split()
finale = bool(match["finale"])
continue_id = int(match["continue"]) if match["continue"] else None
if match["fmt"]:
fmt = list(map(int, match["fmt"].split()))
else:
l = len(teams)
if l <= 5:
fmt = [l]
else:
fmt = [3] * (l // 3 - 1) + [3 + l % 3]
yes = await confirm(
ctx,
self.bot,
f"Le format déterminé est {'+'.join(map(str, fmt))}, "
f"cela est-il correct ?",
) )
if not yes:
raise TfjmError(
"Le tirage est annulé, vous pouvez le recommencer en précisant le format."
)
if not set(fmt).issubset({3, 4, 5}): if not set(fmt).issubset({3, 4, 5}):
raise TfjmError("Seuls les poules à 3, 4 ou 5 équipes sont suportées.") raise TfjmError("Seuls les poules à 3, 4 ou 5 équipes sont suportées.")
teams_roles = [get(ctx.guild.roles, name=tri) for tri in teams]
if not all(teams_roles):
raise TfjmError("Toutes les équipes ne sont pas sur le discord.")
# Here all data should be valid # Here all data should be valid
self.tirages[channel_id] = DiscordTirage(ctx, *teams, fmt=fmt) if continue_id is None:
# New tirage
tirage = DiscordTirage(ctx, *teams_roles, fmt=fmt)
if finale:
rounds = (0,)
else:
rounds = 0, 1
else:
try:
tirage = self.get_tirages()[continue_id]
except KeyError:
raise TfjmError(
f"Il n'y pas de tirage {continue_id}. ID possibles {french_join(self.get_tirages())}"
)
await self.tirages[channel_id].run() rounds = (1,)
tirage.ctx = ctx
tirage.queue = asyncio.Queue()
for i, t in enumerate(teams_roles):
await tirage.event(Event(t.name, i + 1))
self.tirages[channel_id] = tirage
await self.tirages[channel_id].run(rounds)
if self.tirages[channel_id]: if self.tirages[channel_id]:
# Check if aborted in an other way # Check if aborted in an other way
@ -622,10 +677,12 @@ class TirageCog(Cog, name="Tirages"):
@draw_group.command(name="abort") @draw_group.command(name="abort")
@commands.has_any_role(*Role.ORGAS) @commands.has_any_role(*Role.ORGAS)
async def abort_draw_cmd(self, ctx): async def abort_draw_cmd(self, ctx, force: bool = False):
""" """
(orga) Annule le tirage en cours. (orga) Annule le tirage en cours.
Si oui est passé en paramettre, le tirage sera supprímé en même temps.
Le tirage ne pourra pas être continué. Si besoin, Le tirage ne pourra pas être continué. Si besoin,
n'hésitez pas à appeller un @dev : il peut réparer n'hésitez pas à appeller un @dev : il peut réparer
plus de choses qu'on imagine (mais moins qu'on voudrait). plus de choses qu'on imagine (mais moins qu'on voudrait).
@ -633,9 +690,18 @@ class TirageCog(Cog, name="Tirages"):
channel_id = ctx.channel.id channel_id = ctx.channel.id
if channel_id in self.tirages: if channel_id in self.tirages:
await ctx.send(f"Le tirage {self.tirages[channel_id].id} est annulé.") id = self.tirages[channel_id].id
await ctx.send(f"Le tirage {id} est annulé.")
self.tirages[channel_id].save() self.tirages[channel_id].save()
del self.tirages[channel_id] del self.tirages[channel_id]
if force:
tirages = self.get_tirages()
del tirages[id]
File.TIRAGES.touch()
with open(File.TIRAGES, "w") as f:
yaml.dump(tirages, f)
else: else:
await ctx.send("Il n'y a pas de tirage en cours.") await ctx.send("Il n'y a pas de tirage en cours.")
@ -739,6 +805,16 @@ class TirageCog(Cog, name="Tirages"):
await ctx.send(str(resp.reason)) await ctx.send(str(resp.reason))
await ctx.send(await resp.content.read()) await ctx.send(await resp.content.read())
@draw_group.command(name="order")
@commands.has_role(Role.DEV)
async def set_order(self, ctx, *teams: discord.Role):
"""(dev) L'ordre des équipes sera celui du message."""
channel = ctx.channel.id
if channel in self.tirages:
for i, t in enumerate(teams):
await self.tirages[channel].event(Event(t.name, i + 1))
def setup(bot): def setup(bot):
bot.add_cog(TirageCog(bot)) bot.add_cog(TirageCog(bot))

View File

@ -88,6 +88,7 @@ class Emoji:
BIN = "🗑️" BIN = "🗑️"
DICE = "🎲" DICE = "🎲"
CHECK = "" CHECK = ""
CROSS = ""
PLUS_1 = "👍" PLUS_1 = "👍"
MINUS_1 = "👎" MINUS_1 = "👎"

View File

@ -15,7 +15,7 @@ tirages = {}
def start(): def start():
bot = CustomBot(("! ", "!"), case_insensitive=True) bot = CustomBot(("! ", "!"), case_insensitive=True, owner_id=DIEGO)
@bot.event @bot.event
async def on_ready(): async def on_ready():

View File

@ -1,9 +1,15 @@
import asyncio
from pprint import pprint
from functools import wraps from functools import wraps
from io import StringIO
from typing import Union from typing import Union
import discord
import psutil import psutil
from discord.ext.commands import Bot from discord.ext.commands import Bot
from src.constants import *
def fg(text, color: int = 0xFFA500): def fg(text, color: int = 0xFFA500):
r = color >> 16 r = color >> 16
@ -14,6 +20,10 @@ def fg(text, color: int = 0xFFA500):
def french_join(l): def french_join(l):
l = list(l) l = list(l)
if not l:
return ""
if len(l) < 2:
return l[0]
start = ", ".join(l[:-1]) start = ", ".join(l[:-1])
return f"{start} et {l[-1]}" return f"{start} et {l[-1]}"
@ -44,6 +54,45 @@ def send_and_bin(f):
return wrapped return wrapped
async def pprint_send(ctx, *objs, **nobjs):
embed = discord.Embed(title="Debug")
nobjs.update({f"Object {i}": o for i, o in enumerate(objs)})
for name, obj in nobjs.items():
out = StringIO()
pprint(obj, out)
out.seek(0)
value = out.read()
if len(value) > 1000:
value = value[:500] + "\n...\n" + value[-500:]
value = f"```py\n{value}\n```"
embed.add_field(name=name, value=value)
return await ctx.send(embed=embed)
async def confirm(ctx, bot, prompt):
msg: discord.Message = await ctx.send(prompt)
await msg.add_reaction(Emoji.CHECK)
await msg.add_reaction(Emoji.CROSS)
def check(reaction: discord.Reaction, u):
return (
ctx.author == u
and msg.id == reaction.message.id
and str(reaction.emoji) in (Emoji.CHECK, Emoji.CROSS)
)
reaction, u = await bot.wait_for("reaction_add", check=check)
if str(reaction) == Emoji.CHECK:
await msg.clear_reaction(Emoji.CROSS)
return True
else:
await msg.clear_reaction(Emoji.CHECK)
return False
def start_time(): def start_time():
return psutil.Process().create_time() return psutil.Process().create_time()