Compare commits

...

3 Commits

Author SHA1 Message Date
ddorn e0c288289d tourist for final 2020-05-23 20:18:21 +02:00
ddorn fd000958a7 🐛 fix tirages for finale 2020-05-20 12:54:46 +02:00
ddorn 161478caf8 trirages in two parts for finale 2020-05-20 02:52:42 +02:00
8 changed files with 217 additions and 62 deletions

View File

@ -13,6 +13,22 @@ import discord
import yaml import yaml
from src.constants import * from src.constants import *
from src.utils import pprint_send
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):
@ -145,13 +161,15 @@ 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,9 +199,10 @@ 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)
@ -262,7 +281,8 @@ class BaseTirage(yaml.YAMLObject):
i += 1 i += 1
# The conflicts # The conflicts
order = doubles + order order = doubles + order
self.poules[poule] = order
self.poules[poule] = [t.name for t in order]
await self.annonce_poule(poule) await self.annonce_poule(poule)

View File

@ -288,7 +288,7 @@ class DevCog(Cog, name="Dev tools"):
pprint(resp, out) pprint(resp, out)
embed = discord.Embed(title="Result", color=discord.Colour.green()) embed = discord.Embed(title="Result", color=discord.Colour.green())
embed.add_field(name="Query", value=f"```py\n{full_query}```", inline=False) embed.add_field(name="Query", value=f"```py\n{query}```", inline=False)
value = self.to_field_value(out) value = self.to_field_value(out)
if resp is not None and value: if resp is not None and value:

View File

@ -34,7 +34,6 @@ from discord.ext.commands import (
from discord.utils import get from discord.utils import get
from src.constants import * from src.constants import *
from src.constants import Emoji
from src.core import CustomBot from src.core import CustomBot
from src.errors import TfjmError from src.errors import TfjmError
from src.utils import has_role, start_time, send_and_bin from src.utils import has_role, start_time, send_and_bin

View File

@ -91,7 +91,7 @@ class TeamsCog(Cog, name="Teams"):
@commands.command(name="tourist") @commands.command(name="tourist")
@send_and_bin @send_and_bin
async def touriste_cmd(self, ctx: Context, tournoi): async def touriste_cmd(self, ctx: Context, tournoi="Finale"):
""" """
Permet de voir et ecouter dans les salons d'un certain tournoi. Permet de voir et ecouter dans les salons d'un certain tournoi.
@ -99,9 +99,13 @@ class TeamsCog(Cog, name="Teams"):
`!tourist Bordeaux-Nancy` - Donne les droits de spectateurs pour Bordeaux-Nancy `!tourist Bordeaux-Nancy` - Donne les droits de spectateurs pour Bordeaux-Nancy
""" """
if ctx.author.top_role != ctx.guild.default_role: if ctx.author.top_role not in (
ctx.guild.default_role,
get(ctx.guild.roles, name=Role.TOURIST),
):
return f"{ctx.author.mention} tu as déjà un role, devenir spéctateur t'enlèverait des droits." return f"{ctx.author.mention} tu as déjà un role, devenir spéctateur t'enlèverait des droits."
tournoi = tournoi.title()
if tournoi not in TOURNOIS: if tournoi not in TOURNOIS:
return f"{tournoi} n'est pas un nom de tournoi. Possibilités: {french_join(TOURNOIS)}" return f"{tournoi} n'est pas un nom de tournoi. Possibilités: {french_join(TOURNOIS)}"
@ -109,11 +113,16 @@ class TeamsCog(Cog, name="Teams"):
tournoi_role = get(guild.roles, name=tournoi) tournoi_role = get(guild.roles, name=tournoi)
touriste_role = get(guild.roles, name=Role.TOURIST) touriste_role = get(guild.roles, name=Role.TOURIST)
if tournoi == "Finale":
# we don't give the "Finaliste" role to the tourists, it would give them rights.
# to simplify branching we assign twice "Touriste"
touriste_role = touriste_role
await ctx.author.add_roles( await ctx.author.add_roles(
touriste_role, tournoi_role, reason="Demande via le bot." touriste_role, tournoi_role, reason="Demande via le bot."
) )
return f"{ctx.author.mention} à été ajouté comme spectateur dans à {tournoi}." return f"{ctx.author.mention} à été ajouté comme spectateur pour {tournoi}."
@group(name="team", invoke_without_command=True, case_insensitive=True, hidden=True) @group(name="team", invoke_without_command=True, case_insensitive=True, hidden=True)
async def team(self, ctx): async def team(self, ctx):

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
@ -275,8 +279,8 @@ class DiscordTirage(BaseTirage):
if len(teams) == 3: if len(teams) == 3:
table = """``` table = """```
Phase 1 Phase 2 Phase 3 T F Phase 1 Phase 2 Phase 3
Pb {0.pb} Pb {1.pb} Pb {2.pb} J M Pb. {0.pb} Pb. {1.pb} Pb. {2.pb}
{0.name} Def Rap Opp {0.name} Def Rap Opp
@ -300,22 +304,22 @@ class DiscordTirage(BaseTirage):
+-----+---------+---------+---------+---------+```""" +-----+---------+---------+---------+---------+```"""
elif len(teams) == 5: elif len(teams) == 5:
table = """``` table = """```
Phase 1 Phase 2 Phase 3 Phase 1 Phase 2 Phase 3
Salle 1 Salle 2 Salle 1 Salle 2 Salle 1 Salle 1 Salle 2 Salle 1 Salle 2 Salle 1
Pb {0.pb} Pb {1.pb} Pb {2.pb} Pb {3.pb} Pb {4.pb} Pb. {0.pb} Pb. {1.pb} Pb. {2.pb} Pb. {3.pb} Pb. {4.pb}
{0.name} Def Opp Rap {0.name} Def Opp Rap
{1.name} Def Rap Opp {1.name} Def Rap Opp
{2.name} Opp Def Rap {2.name} Opp Def Rap
{3.name} Rap Opp Def {3.name} Rap Opp Def
{4.name} Rap Opp Def {4.name} Rap Opp Def
```""" ```"""
else: else:
table = "WTF il n'y a pas 3,4 ou 5 equipes ici." table = "WTF il n'y a pas 3,4 ou 5 equipes ici."
@ -532,7 +536,8 @@ class TirageCog(Cog, name="Tirages"):
await ctx.send(f"Le problème tiré est... **{problem}**") await ctx.send(f"Le problème tiré est... **{problem}**")
@commands.command( @commands.command(
name="oui", aliases=["accept", "yes", "o", "oh-yeaaah", "accepte", "ouiiiiiii"], name="oui",
aliases=["accept", "yes", "o", "oh-yeaaah", "accepte", "ouiiiiiii", "owi"],
) )
async def accept_cmd(self, ctx): async def accept_cmd(self, ctx):
""" """
@ -578,17 +583,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 +607,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:
if match is None:
await ctx.send("La commande est mal formée.")
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( raise TfjmError(
"Le premier argument doit être le format du tournoi, " "Le tirage est annulé, vous pouvez le recommencer en précisant le format."
"par exemple `3+3` pour deux poules à trois équipes"
) )
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 +678,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 +691,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 +806,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()