diff --git a/src/base_tirage.py b/src/base_tirage.py index f949169..27d9508 100644 --- a/src/base_tirage.py +++ b/src/base_tirage.py @@ -3,12 +3,14 @@ import random import sys import traceback from functools import wraps +from pathlib import Path from pprint import pprint from io import StringIO from typing import Type, Union, Dict, List import discord +import yaml from src.constants import * @@ -70,7 +72,9 @@ class Team: # """ -class Poule: +class Poule(yaml.YAMLObject): + yaml_tag = "Poule" + def __init__(self, poule, rnd): self.poule = poule self.rnd = rnd @@ -79,15 +83,17 @@ class Poule: return f"{self.poule}{self.rnd + 1}" -class BaseTirage: +class BaseTirage(yaml.YAMLObject): + yaml_tag = "Tirage" + def __init__(self, *teams: discord.Role, fmt=(3, 3)): - assert sum(fmt) == len(teams) + assert sum(fmt) == len(teams), "Different number of teams and format" 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.""" + """A mapping between the poule and the list of teams in this poule.""" async def event(self, event: Event): event.set() @@ -153,8 +159,9 @@ class BaseTirage: while None in dices.values(): event = await self.next(int) - # TODO: avoid KeyError - if dices[event.team] is None: + if event.team not in dices: + await self.warn_wrong_team(None, event.team) + elif dices[event.team] is None: dices[event.team] = event.value await self.info_dice(event.team, event.value) else: diff --git a/src/cogs/tirages.py b/src/cogs/tirages.py index 740d8fc..f66599b 100644 --- a/src/cogs/tirages.py +++ b/src/cogs/tirages.py @@ -17,20 +17,16 @@ from discord.ext import commands from discord.ext.commands import group, Cog, Context from discord.utils import get -from src.base_tirage import BaseTirage, Event +from src.base_tirage import BaseTirage, Event, Poule from src.constants import * from src.core import CustomBot from src.errors import TfjmError, UnwantedCommand -__all__ = ["Tirage", "TirageCog"] +__all__ = ["TirageCog"] from src.utils import send_and_bin, french_join -def in_passage_order(teams, round=0): - return sorted(teams, key=lambda team: team.passage_order[round] or 0, reverse=True) - - Record = namedtuple("Record", ["name", "pb", "penalite"]) @@ -79,6 +75,36 @@ class DiscordTirage(BaseTirage): self.ctx = ctx self.captain_mention = get(ctx.guild.roles, name=Role.CAPTAIN).mention + ts = self.load_all() + self.id = 1 + (max(ts) if ts else 0) + self.save() + + @staticmethod + def load_all(): + if not File.TIRAGES.exists(): + return {} + + with open(File.TIRAGES) as f: + tirages = yaml.load(f) + + return tirages + + def save(self): + ts = self.load_all() + + ctx = self.ctx + queue = self.queue + self.ctx = None + self.queue = None + ts[self.id] = self + + File.TIRAGES.touch() + with open(File.TIRAGES, "w") as f: + yaml.dump(ts, f) + + self.ctx = ctx + self.queue = queue + def team_for(self, author): for team in self.teams: if get(author.roles, name=team): @@ -256,8 +282,7 @@ class DiscordTirage(BaseTirage): | {1.name} | Opp | Déf | Rap | +-----+---------+---------+---------+ | {2.name} | Rap | Opp | Déf | -+-----+---------+---------+---------+ - ```""" ++-----+---------+---------+---------+```""" else: table = """``` +-----+---------+---------+---------+---------+ @@ -271,8 +296,7 @@ class DiscordTirage(BaseTirage): | {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])}", @@ -293,11 +317,13 @@ class DiscordTirage(BaseTirage): ) embed.set_footer( - text="Ce tirage peut être affiché à tout moment avec `!draw show XXX`" + text=f"Ce tirage peut être affiché à tout moment avec `!draw show {self.id}`" ) await self.ctx.send(embed=embed) + self.save() + @safe @send_all async def info_start(self): @@ -327,7 +353,6 @@ class DiscordTirage(BaseTirage): "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 @safe @@ -380,170 +405,10 @@ class DiscordTirage(BaseTirage): msg += "!" await self.ctx.send(msg) - -class TiragePhase: - """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): - team = self.current_team - if self.team_for(author) != team: - raise UnwantedCommand( - f"C'est à {team.name} 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)`." - ) - - # Choose an *available* problem - problems = [ - p for p in PROBLEMS if self.available(p) and not p in team.accepted_problems - ] - problem = random.choice(problems) - - 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: - pass - else: - if yes: - team.accepted_problems[self.round] = team.drawn_problem - else: - team.rejected[self.round].add(team.drawn_problem) - - 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 - - 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 + async def show(self, ctx): + self.ctx = ctx + for poule in self.poules: + await self.annonce_poule(poule) class TirageCog(Cog, name="Tirages"): @@ -715,6 +580,7 @@ class TirageCog(Cog, name="Tirages"): # Here all data should be valid self.tirages[channel_id] = DiscordTirage(ctx, *teams, fmt=fmt) + await self.tirages[channel_id].run() if self.tirages[channel_id]: @@ -762,13 +628,12 @@ class TirageCog(Cog, name="Tirages"): # await tirage.update_phase(ctx) def get_tirages(self) -> Dict[int, BaseTirage]: - if not File.TIRAGES.exists(): - return {} + return DiscordTirage.load_all() - with open(File.TIRAGES) as f: - tirages = yaml.load(f) - - return tirages + def save_tirages(self, tirages): + File.TIRAGES.touch() + with open(File.TIRAGES, "w") as f: + yaml.dump(tirages, f) @draw_group.command(name="show") async def show_cmd(self, ctx: Context, tirage_id: str = "all"): @@ -780,7 +645,6 @@ class TirageCog(Cog, name="Tirages"): `!draw show 42` - Affiche le tirage n°42 """ - return tirages = self.get_tirages() if not tirages: @@ -793,8 +657,7 @@ class TirageCog(Cog, name="Tirages"): "Vous pouvez en consulter un en particulier avec `!draw show ID`." ) msg = "\n".join( - f"`{key}`: {', '.join(team.name for team in tirage.teams)}" - for key, tirage in tirages.items() + f"`{key}`: {', '.join(tirage.teams)}" for key, tirage in tirages.items() ) await ctx.send(msg) else: