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
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):
@ -145,14 +161,16 @@ class BaseTirage(yaml.YAMLObject):
return event
event.clear()
async def run(self):
async def run(self, rounds=(0, 1)):
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:
await self.draw_poule(poule)
for poule in new_poules:
await self.draw_poule(poule)
await self.info_finish()
@ -181,19 +199,20 @@ class BaseTirage(yaml.YAMLObject):
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 = {}
for rnd in (0, 1):
await self.start_make_poule(rnd)
await self.start_make_poule(rnd)
dices = await self.get_dices(self.teams)
sorted_teams = sorted(self.teams, key=lambda t: dices[t])
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
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
@ -262,7 +281,8 @@ class BaseTirage(yaml.YAMLObject):
i += 1
# The conflicts
order = doubles + order
self.poules[poule] = order
self.poules[poule] = [t.name for t in order]
await self.annonce_poule(poule)

View File

@ -288,7 +288,7 @@ class DevCog(Cog, name="Dev tools"):
pprint(resp, out)
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)
if resp is not None and value:

View File

@ -34,7 +34,6 @@ from discord.ext.commands import (
from discord.utils import get
from src.constants import *
from src.constants import Emoji
from src.core import CustomBot
from src.errors import TfjmError
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")
@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.
@ -99,9 +99,13 @@ class TeamsCog(Cog, name="Teams"):
`!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."
tournoi = tournoi.title()
if tournoi not in 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)
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(
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)
async def team(self, ctx):

View File

@ -2,6 +2,7 @@
import asyncio
import random
import re
import sys
import traceback
from collections import defaultdict, namedtuple
@ -25,8 +26,11 @@ from src.errors import TfjmError, UnwantedCommand
__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"])
@ -244,7 +248,9 @@ class DiscordTirage(BaseTirage):
@safe
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
@send_all
@ -255,12 +261,10 @@ class DiscordTirage(BaseTirage):
second = "\n".join(
f"{p}: {french_join(t)}" for p, t in poules.items() if p.rnd == 1
)
yield (
f"Les poules sont donc, pour le premier tour :"
f"```{first}```\n"
f"Et pour le second tour :"
f"```{second}```"
)
if first:
yield (f"Les poules sont donc, pour le premier tour :" f"```{first}```\n")
if second:
yield (f"Pour le second tour les poules sont :" f"```{second}```")
@safe
@send_all
@ -275,8 +279,8 @@ class DiscordTirage(BaseTirage):
if len(teams) == 3:
table = """```
Phase 1 Phase 2 Phase 3
Pb {0.pb} Pb {1.pb} Pb {2.pb}
T F Phase 1 Phase 2 Phase 3
J M Pb. {0.pb} Pb. {1.pb} Pb. {2.pb}
{0.name} Def Rap Opp
@ -288,7 +292,7 @@ class DiscordTirage(BaseTirage):
table = """```
+-----+---------+---------+---------+---------+
| | Phase 1 | Phase 2 | Phase 3 | Phase 4 |
| | Pb {0.pb} | Pb {1.pb} | Pb {2.pb} | Pb {3.pb} |
| | Pb {0.pb} | Pb {1.pb} | Pb {2.pb} | Pb {3.pb} |
+-----+---------+---------+---------+---------+
| {0.name} | Déf | | Rap | Opp |
+-----+---------+---------+---------+---------+
@ -300,22 +304,22 @@ class DiscordTirage(BaseTirage):
+-----+---------+---------+---------+---------+```"""
elif len(teams) == 5:
table = """```
Phase 1 Phase 2 Phase 3
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}
{0.name} Def Opp Rap
{1.name} Def Rap Opp
{2.name} Opp Def Rap
{3.name} Rap Opp Def
{4.name} Rap Opp Def
```"""
Phase 1 Phase 2 Phase 3
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}
{0.name} Def Opp Rap
{1.name} Def Rap Opp
{2.name} Opp Def Rap
{3.name} Rap Opp Def
{4.name} Rap Opp Def
```"""
else:
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}**")
@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):
"""
@ -578,17 +583,20 @@ class TirageCog(Cog, name="Tirages"):
await ctx.invoke(self.bot.get_command("help"), "draw")
@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)
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.
Cette commande attend des trigrames d'équipes.
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
@ -599,22 +607,70 @@ class TirageCog(Cog, name="Tirages"):
"il est possible d'en commencer un autre sur une autre channel."
)
try:
fmt = list(map(int, fmt.split("+")))
except ValueError:
raise TfjmError(
"Le premier argument doit être le format du tournoi, "
"par exemple `3+3` pour deux poules à trois équipes"
query = " ".join(args)
match = re.match(RE_DRAW_START, query)
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(
"Le tirage est annulé, vous pouvez le recommencer en précisant le format."
)
if not set(fmt).issubset({3, 4, 5}):
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
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]:
# Check if aborted in an other way
@ -622,10 +678,12 @@ class TirageCog(Cog, name="Tirages"):
@draw_group.command(name="abort")
@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.
Si oui est passé en paramettre, le tirage sera supprímé en même temps.
Le tirage ne pourra pas être continué. Si besoin,
n'hésitez pas à appeller un @dev : il peut réparer
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
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()
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:
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(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):
bot.add_cog(TirageCog(bot))

View File

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

View File

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

View File

@ -1,9 +1,15 @@
import asyncio
from pprint import pprint
from functools import wraps
from io import StringIO
from typing import Union
import discord
import psutil
from discord.ext.commands import Bot
from src.constants import *
def fg(text, color: int = 0xFFA500):
r = color >> 16
@ -14,6 +20,10 @@ def fg(text, color: int = 0xFFA500):
def french_join(l):
l = list(l)
if not l:
return ""
if len(l) < 2:
return l[0]
start = ", ".join(l[:-1])
return f"{start} et {l[-1]}"
@ -44,6 +54,45 @@ def send_and_bin(f):
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():
return psutil.Process().create_time()