mirror of
				https://gitlab.com/ddorn/tfjm-discord-bot.git
				synced 2025-10-31 06:59:52 +01:00 
			
		
		
		
	🚧 WIP: better and more flexible tirages
This commit is contained in:
		
							
								
								
									
										264
									
								
								src/base_tirage.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								src/base_tirage.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,264 @@ | |||||||
|  | import asyncio | ||||||
|  | import random | ||||||
|  | from pprint import pprint | ||||||
|  |  | ||||||
|  | from io import StringIO | ||||||
|  | from typing import Type, Union, Dict, List | ||||||
|  |  | ||||||
|  | import discord | ||||||
|  |  | ||||||
|  | from src.constants import * | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Event(asyncio.Event): | ||||||
|  |     def __init__(self, team: str, value: Union[bool, int, str]): | ||||||
|  |         super(Event, self).__init__() | ||||||
|  |         self.value = value | ||||||
|  |         self.team = team | ||||||
|  |         self.response = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Team: | ||||||
|  |     yaml_tag = "Team" | ||||||
|  |  | ||||||
|  |     def __init__(self, team_role): | ||||||
|  |         self.name = team_role.name | ||||||
|  |         self.mention = team_role.mention | ||||||
|  |  | ||||||
|  |         self.accepted_problems = [None, None] | ||||||
|  |         self.rejected = [set(), set()] | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         s = StringIO() | ||||||
|  |         pprint(self.__dict__, stream=s) | ||||||
|  |         s.seek(0) | ||||||
|  |         return s.read() | ||||||
|  |  | ||||||
|  |     __repr__ = __str__ | ||||||
|  |  | ||||||
|  |     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): | ||||||
|  |  | ||||||
|  |         info = { | ||||||
|  |             # "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 passage": self.passage_order[round], | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         width = max(map(len, info)) | ||||||
|  |  | ||||||
|  |         return "\n".join(f"`{n.rjust(width)}`: {v}" for n, v in info.items()) | ||||||
|  |  | ||||||
|  |         return f""" - 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 Poule: | ||||||
|  |     def __init__(self, poule, rnd): | ||||||
|  |         self.poule = poule | ||||||
|  |         self.rnd = rnd | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return f"{self.poule}{self.rnd + 1}" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BaseTirage: | ||||||
|  |     def __init__(self, *teams: discord.Role, fmt=(3, 3)): | ||||||
|  |         assert sum(fmt) == len(teams) | ||||||
|  |  | ||||||
|  |         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.""" | ||||||
|  |  | ||||||
|  |     async def event(self, event: Event): | ||||||
|  |         event.set() | ||||||
|  |         await self.queue.put(event) | ||||||
|  |         await event.wait() | ||||||
|  |         return event.response | ||||||
|  |  | ||||||
|  |     async def dice(self, trigram): | ||||||
|  |         return await self.event(Event(trigram, random.randint(1, 100))) | ||||||
|  |  | ||||||
|  |     async def rproblem(self, trigram): | ||||||
|  |         team = self.teams[trigram] | ||||||
|  |         rnd = 0 if team.accepted_problems[0] is None else 1 | ||||||
|  |         for poule, teams in self.poules.items(): | ||||||
|  |             if trigram in teams and poule.rnd == rnd: | ||||||
|  |                 break | ||||||
|  |         else: | ||||||
|  |             return await self.warn_wrong_team(None, trigram) | ||||||
|  |  | ||||||
|  |         other_pbs = [self.teams[team].accepted_problems[rnd] for team in teams] | ||||||
|  |         available = [ | ||||||
|  |             pb | ||||||
|  |             for pb in PROBLEMS | ||||||
|  |             if pb not in team.accepted_problems and pb not in other_pbs | ||||||
|  |         ] | ||||||
|  |         return await self.event(Event(trigram, random.choice(available))) | ||||||
|  |  | ||||||
|  |     async def next(self, typ, team=None): | ||||||
|  |         while True: | ||||||
|  |             event = await self.queue.get() | ||||||
|  |             if team is not None and event.team != team: | ||||||
|  |                 await self.warn_wrong_team(team, event.team) | ||||||
|  |             elif not isinstance(event.value, typ): | ||||||
|  |                 await self.warn_unwanted(typ, event.value) | ||||||
|  |             else: | ||||||
|  |                 event.clear() | ||||||
|  |                 return event | ||||||
|  |             event.clear() | ||||||
|  |  | ||||||
|  |     async def run(self): | ||||||
|  |  | ||||||
|  |         await self.info_start() | ||||||
|  |  | ||||||
|  |         self.poules = await self.make_poules() | ||||||
|  |  | ||||||
|  |         for poule in self.poules: | ||||||
|  |             await self.draw_poule(poule) | ||||||
|  |  | ||||||
|  |         await self.info_finish() | ||||||
|  |  | ||||||
|  |     async def get_dices(self, teams): | ||||||
|  |         dices = {t: None for t in teams} | ||||||
|  |         collisions = list(teams) | ||||||
|  |         while collisions: | ||||||
|  |  | ||||||
|  |             for t in collisions: | ||||||
|  |                 dices[t] = None | ||||||
|  |             collisions = [] | ||||||
|  |  | ||||||
|  |             while None in dices.values(): | ||||||
|  |                 event = await self.next(int) | ||||||
|  |  | ||||||
|  |                 # TODO: avoid KeyError | ||||||
|  |                 if dices[event.team] is None: | ||||||
|  |                     dices[event.team] = event.value | ||||||
|  |                 else: | ||||||
|  |                     await self.warn_twice(int) | ||||||
|  |  | ||||||
|  |             if collisions: | ||||||
|  |                 await self.warn_colisions(collisions) | ||||||
|  |         return dices | ||||||
|  |  | ||||||
|  |     async def make_poules(self): | ||||||
|  |         poules = {} | ||||||
|  |         for rnd in (0, 1): | ||||||
|  |             await self.start_make_poule(rnd) | ||||||
|  |  | ||||||
|  |             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 | ||||||
|  |  | ||||||
|  |         await self.annonce_poules(poules) | ||||||
|  |         return poules | ||||||
|  |  | ||||||
|  |     async def draw_poule(self, poule): | ||||||
|  |         # Trigrams in draw order | ||||||
|  |         trigrams = await self.draw_order(poule) | ||||||
|  |  | ||||||
|  |         # Teams in draw order | ||||||
|  |         teams = [self.teams[tri] for tri in trigrams] | ||||||
|  |         current = 0 | ||||||
|  |         while not all(team.accepted_problems[poule.rnd] for team in teams): | ||||||
|  |             team = teams[current] | ||||||
|  |             if team.accepted_problems[poule.rnd] is not None: | ||||||
|  |                 # The team already accepted a problem | ||||||
|  |                 current += 1 | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             # Choose problem | ||||||
|  |             await self.start_select_pb(team) | ||||||
|  |             event = await self.next(str, team.name) | ||||||
|  |             # TODO: Add check for already selected / taken by someone else | ||||||
|  |             # This is not a bug for now, since it cannot happen yet | ||||||
|  |             await self.info_draw_pb(team, event.value, rnd) | ||||||
|  |  | ||||||
|  |             # Accept it | ||||||
|  |             accept = await self.next(bool, team.name) | ||||||
|  |             if accept: | ||||||
|  |                 team.accepted_problems[poule.rnd] = event.value | ||||||
|  |                 await self.info_accepted(team, event.value) | ||||||
|  |             else: | ||||||
|  |                 await self.info_rejected(team, event.value, rnd=poule.rnd) | ||||||
|  |                 team.rejected[poule.rnd].add(event.value) | ||||||
|  |  | ||||||
|  |             current += 1 | ||||||
|  |  | ||||||
|  |         await self.annonce_poule(poule) | ||||||
|  |  | ||||||
|  |     async def draw_order(self, poule): | ||||||
|  |         await self.start_draw_order(poule) | ||||||
|  |  | ||||||
|  |         teams = self.poules[poule] | ||||||
|  |         dices = await self.get_dices(teams) | ||||||
|  |  | ||||||
|  |         order = sorted(self.teams, key=lambda t: dices[t], reverse=True) | ||||||
|  |  | ||||||
|  |         await self.annonce_draw_order(order) | ||||||
|  |         return order | ||||||
|  |  | ||||||
|  |     async def warn_unwanted(self, wanted: Type, got: Type): | ||||||
|  |         """Called when a event of an unwanted type occurs.""" | ||||||
|  |  | ||||||
|  |     async def warn_wrong_team(self, expected, got): | ||||||
|  |         """Called when a team that should not play now put an event""" | ||||||
|  |  | ||||||
|  |     async def warn_colisions(self, collisions: List[str]): | ||||||
|  |         """Called when there are collisions in a dice tirage.""" | ||||||
|  |  | ||||||
|  |     async def warn_twice(self, typ: Type): | ||||||
|  |         """Called when an event appears once again and not wanted.""" | ||||||
|  |  | ||||||
|  |     async def start_make_poule(self, rnd): | ||||||
|  |         """Called when it starts drawing the poules for round `rnd`""" | ||||||
|  |  | ||||||
|  |     async def start_draw_order(self, poule): | ||||||
|  |         """Called when we start to draw the order.""" | ||||||
|  |  | ||||||
|  |     async def start_select_pb(self, team): | ||||||
|  |         """Called when a team needs to select a problem.""" | ||||||
|  |  | ||||||
|  |     async def annonce_poules(self, poules): | ||||||
|  |         """Called when all poules are defined.""" | ||||||
|  |  | ||||||
|  |     async def annonce_draw_order(self, order): | ||||||
|  |         """Called when the drawing order is defined.""" | ||||||
|  |  | ||||||
|  |     async def annonce_poule(self, poule): | ||||||
|  |         """Called when the problems and order for a poule is known.""" | ||||||
|  |  | ||||||
|  |     async def info_start(self): | ||||||
|  |         """Called at the start of the tirage.""" | ||||||
|  |  | ||||||
|  |     async def info_finish(self): | ||||||
|  |         """Called when the tirage has ended.""" | ||||||
|  |  | ||||||
|  |     async def info_draw_pb(self, team, pb, rnd): | ||||||
|  |         """Called when a team draws a problem.""" | ||||||
|  |  | ||||||
|  |     async def info_accepted(self, team, pb): | ||||||
|  |         """Called when a team accepts a problem.""" | ||||||
|  |  | ||||||
|  |     async def info_rejected(self, team, pb, rnd): | ||||||
|  |         """Called when a team rejects a problem, | ||||||
|  |         before it is added to the rejected set.""" | ||||||
| @@ -3,9 +3,11 @@ | |||||||
| import asyncio | import asyncio | ||||||
| import random | import random | ||||||
| from collections import defaultdict, namedtuple | from collections import defaultdict, namedtuple | ||||||
|  | from dataclasses import dataclass | ||||||
|  | from functools import wraps | ||||||
| from io import StringIO | from io import StringIO | ||||||
| from pprint import pprint | from pprint import pprint | ||||||
| from typing import Type, Dict | from typing import Type, Dict, Union, Optional, List | ||||||
|  |  | ||||||
| import discord | import discord | ||||||
| import yaml | import yaml | ||||||
| @@ -13,13 +15,14 @@ from discord.ext import commands | |||||||
| from discord.ext.commands import group, Cog, Context | from discord.ext.commands import group, Cog, Context | ||||||
| from discord.utils import get | from discord.utils import get | ||||||
|  |  | ||||||
|  | from src.base_tirage import BaseTirage | ||||||
| from src.constants import * | from src.constants import * | ||||||
| from src.core import CustomBot | from src.core import CustomBot | ||||||
| from src.errors import TfjmError, UnwantedCommand | from src.errors import TfjmError, UnwantedCommand | ||||||
|  |  | ||||||
| __all__ = ["Tirage", "TirageCog"] | __all__ = ["Tirage", "TirageCog"] | ||||||
|  |  | ||||||
| from src.utils import send_and_bin | from src.utils import send_and_bin, french_join | ||||||
|  |  | ||||||
|  |  | ||||||
| def in_passage_order(teams, round=0): | def in_passage_order(teams, round=0): | ||||||
| @@ -29,54 +32,246 @@ def in_passage_order(teams, round=0): | |||||||
| Record = namedtuple("Record", ["name", "pb", "penalite"]) | Record = namedtuple("Record", ["name", "pb", "penalite"]) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Team(yaml.YAMLObject): | def delete_and_pm(f): | ||||||
|     yaml_tag = "Team" |     @wraps(f) | ||||||
|  |     def wrapper(self, *args, **kwargs): | ||||||
|  |         await self.ctx.message.delete() | ||||||
|  |         await self.ctx.author.send( | ||||||
|  |             "J'ai supprimé ton message:\n> " | ||||||
|  |             + self.ctx.message.clean_content | ||||||
|  |             + "\nC'est pas grave, c'est juste pour ne pas encombrer " | ||||||
|  |             "le chat lors du tirage." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def __init__(self, ctx, team_role): |         msg = await f(self, *args, **kwargs) | ||||||
|         self.name = team_role.name |         if msg: | ||||||
|         self.mention = team_role.mention |             await self.ctx.author.send(f"Raison: {msg}") | ||||||
|         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 __str__(self): | def send_all(f): | ||||||
|         s = StringIO() |     @wraps(f) | ||||||
|         pprint(self.__dict__, stream=s) |     async def wrapper(self, *args, **kwargs): | ||||||
|         s.seek(0) |         async for msg in f(self, *args, **kwargs): | ||||||
|         return s.read() |             await self.ctx.send(msg) | ||||||
|  |  | ||||||
|     __repr__ = __str__ |     return wrapper | ||||||
|  |  | ||||||
|     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): | class DiscordTirage(BaseTirage): | ||||||
|  |     def __init__(self, ctx, *teams, fmt): | ||||||
|  |         super(DiscordTirage, self).__init__(*teams, fmt=fmt) | ||||||
|  |         self.ctx = ctx | ||||||
|  |         self.captain_mention = get(ctx.guild.roles, name=Role.CAPTAIN).mention | ||||||
|  |  | ||||||
|         info = { |     def records(self, teams, rnd): | ||||||
|             # "Accepté": self.accepted_problems[round], |         """Get the strings needed for show the tirage in a list of Records""" | ||||||
|             "Refusés": ", ".join(p[0] for p in self.rejected[round]) |  | ||||||
|             if self.rejected[round] |         return [ | ||||||
|             else "aucun", |             Record( | ||||||
|             "Coefficient": self.coeff(round), |                 team.name, | ||||||
|             # "Ordre passage": self.passage_order[round], |                 (team.accepted_problems[rnd] or "- None")[0], | ||||||
|  |                 f"k = {team.coeff(rnd)} ", | ||||||
|  |             ) | ||||||
|  |             for team in teams | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |     @delete_and_pm | ||||||
|  |     async def warn_unwanted(self, wanted: Type, got: Type): | ||||||
|  |  | ||||||
|  |         texts = { | ||||||
|  |             (int, str): "Il faut tirer un problème avec `!rp` et pas un dé.", | ||||||
|  |             (int, bool): "Il faut accepter `!oui` ou refuser `!non` " | ||||||
|  |             "le problème d'abord.", | ||||||
|  |             (str, int): "Tu dois lancer un dé (`!dice 100`), pas choisir un problème.", | ||||||
|  |             (str, bool): "Il faut accepter `!oui` ou refuser `!non` le " | ||||||
|  |             "problème avant d'en choisir un autre", | ||||||
|  |             (bool, str): "Tu es bien optimiste pour vouloir accepter un problème " | ||||||
|  |             "avant de l'avoir tiré !" | ||||||
|  |             if got | ||||||
|  |             else "Halte là ! Ce serait bien de tirer un problème d'abord... " | ||||||
|  |             "et peut-être qu'il te plaira :) ", | ||||||
|  |             (bool, int): "Il tirer un dé avec `!dice 100` d'abord.", | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         width = max(map(len, info)) |         reason = texts.get((type(got), wanted)) | ||||||
|  |  | ||||||
|         return "\n".join(f"`{n.rjust(width)}`: {v}" for n, v in info.items()) |         if reason is None: | ||||||
|  |             print(f"Weird, arguments for warn_unwanted were {wanted} and {got}") | ||||||
|  |             reason = "Je sais pas, le code ne devrait pas venir ici..." | ||||||
|  |         return reason | ||||||
|  |  | ||||||
|         return f""" - Accepté: {self.accepted_problems[round]} |     @delete_and_pm | ||||||
|  - Refusés: {", ".join(p[0] for p in self.rejected[round]) if self.rejected[round] else "aucun"} |     async def warn_wrong_team(self, expected, got): | ||||||
|  - Coefficient: {self.coeff(round)} |         return "ce n'était pas à ton tour." | ||||||
|  - Ordre au tirage: {self.tirage_order[round]} |  | ||||||
|  - Ordre de passage: {self.passage_order[round]} |     async def warn_colisions(self, collisions: List[str]): | ||||||
| """ |         await self.ctx.send( | ||||||
|  |             f"Les equipes {french_join(collisions)} ont fait le même résultat " | ||||||
|  |             "et doivent relancer un dé. " | ||||||
|  |             "Le nouveau lancer effacera l'ancien." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     @delete_and_pm | ||||||
|  |     async def warn_twice(self, typ: Type): | ||||||
|  |  | ||||||
|  |         if typ == int: | ||||||
|  |             return "Tu as déjà lancé un dé, pas besoin de le refaire ;)" | ||||||
|  |  | ||||||
|  |         print("Weird, DiscordTirage.warn_twice was called with", typ) | ||||||
|  |         return "Je sais pas, le code ne devrait pas venir ici..." | ||||||
|  |  | ||||||
|  |     @send_all | ||||||
|  |     async def start_make_poule(self, rnd): | ||||||
|  |         if rnd == 0: | ||||||
|  |             yield ( | ||||||
|  |                 f"Les {self.captain_mention}s, vous pouvez désormais tous lancer un dé 100 " | ||||||
|  |                 "comme ceci : `!dice 100`. " | ||||||
|  |                 "Les poules et l'ordre de passage lors du premier tour sera l'ordre croissant des dés, " | ||||||
|  |                 "c'est-à-dire que le plus petit lancer sera le premier à passer dans la poule A." | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             yield ( | ||||||
|  |                 f"Les {self.captain_mention}s, vous pouvez à nouveau tous lancer un dé 100, " | ||||||
|  |                 f"afin de déterminer les poules du second tour." | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     async def start_draw_order(self, poule): | ||||||
|  |         print(poule) | ||||||
|  |  | ||||||
|  |     async def start_select_pb(self, team): | ||||||
|  |         await self.ctx.send(f"C'est au tour de {team.mention} de choisir un problème.") | ||||||
|  |  | ||||||
|  |     async def annonce_poules(self, poules): | ||||||
|  |         print(poules) | ||||||
|  |  | ||||||
|  |     @send_all | ||||||
|  |     async def annonce_draw_order(self, order): | ||||||
|  |         order_str = "\n ".join(f"{i}) {tri}" for i, tri in enumerate(order)) | ||||||
|  |         yield "L'ordre de tirage des problèmes pour ce tour est donc: \n" + order_str | ||||||
|  |  | ||||||
|  |     async def annonce_poule(self, poule): | ||||||
|  |         teams = [self.teams[tri] for tri in self.poules[poule]] | ||||||
|  |  | ||||||
|  |         if len(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   | | ||||||
|  |             +-----+---------+---------+---------+---------+ | ||||||
|  |             | {1.name} |   Opp   |   Déf   |         |   Rap   | | ||||||
|  |             +-----+---------+---------+---------+---------+ | ||||||
|  |             | {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])}", | ||||||
|  |             color=EMBED_COLOR, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         embed.add_field( | ||||||
|  |             name=ROUND_NAMES[poule.rnd].capitalize(), | ||||||
|  |             value=table.format(*self.records(teams, poule.rnd)), | ||||||
|  |             inline=False, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         for team in teams: | ||||||
|  |             embed.add_field( | ||||||
|  |                 name=team.name + " - " + team.accepted_problems[poule.rnd], | ||||||
|  |                 value=team.details(poule.rnd), | ||||||
|  |                 inline=True, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         embed.set_footer( | ||||||
|  |             text="Ce tirage peut être affiché à tout moment avec `!draw show XXX`" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         await self.ctx.send(embed=embed) | ||||||
|  |  | ||||||
|  |     @send_all | ||||||
|  |     async def info_start(self): | ||||||
|  |         yield ( | ||||||
|  |             "Nous allons commencer le tirage des problèmes. " | ||||||
|  |             "Seuls les capitaines de chaque équipe peuvent désormais écrire ici. " | ||||||
|  |             "Merci de d'envoyer seulement ce que est nécessaire et suffisant au " | ||||||
|  |             "bon déroulement du tirage. Vous pouvez à tout moment poser toute question " | ||||||
|  |             "si quelque chose n'est pas clair ou ne va pas." | ||||||
|  |             "\n" | ||||||
|  |             "\n" | ||||||
|  |             "Pour plus de détails sur le déroulement du tirgae au sort, le règlement " | ||||||
|  |             "est accessible sur https://tfjm.org/reglement." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         yield ( | ||||||
|  |             "Nous allons d'abord tirer les poules et l'ordre de passage dans chaque tour " | ||||||
|  |             "avec toutes les équipes puis pour chaque poule de chaque tour, nous tirerons " | ||||||
|  |             "l'ordre de tirage pour le tour et les problèmes." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     @send_all | ||||||
|  |     async def info_finish(self): | ||||||
|  |         yield "Le tirage est fini, merci à tout le monde !" | ||||||
|  |         yield ( | ||||||
|  |             "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 | ||||||
|  |  | ||||||
|  |     async def info_draw_pb(self, team, pb, rnd): | ||||||
|  |         if pb in team.rejected[rnd]: | ||||||
|  |             await self.ctx.send( | ||||||
|  |                 f"Vous avez déjà refusé **{pb}**, " | ||||||
|  |                 f"vous pouvez le refuser à nouveau (`!non`) et " | ||||||
|  |                 f"tirer immédiatement un nouveau problème " | ||||||
|  |                 f"ou changer d'avis et l'accepter (`!oui`)." | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             if len(team.rejected[rnd]) >= MAX_REFUSE: | ||||||
|  |                 await self.ctx.send( | ||||||
|  |                     f"Vous pouvez accepter ou refuser **{pb}** " | ||||||
|  |                     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 self.ctx.send( | ||||||
|  |                     f"Vous pouvez accepter (`!oui`) ou refuser (`!non`) **{pb}**. " | ||||||
|  |                     f"Il reste {MAX_REFUSE - len(team.rejected[rnd])} refus sans pénalité " | ||||||
|  |                     f"pour {team.mention}." | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |     async def info_accepted(self, team, pb): | ||||||
|  |         await self.ctx.send( | ||||||
|  |             f"L'équipe {team.mention} a accepté " | ||||||
|  |             f"**{pb}** ! Les autres équipes " | ||||||
|  |             f"ne peuvent plus l'accepter." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     async def info_rejected(self, team, pb, rnd): | ||||||
|  |         msg = f"{team.mention} a refusé **{pb}** " | ||||||
|  |         if pb in team.rejected[rnd]: | ||||||
|  |             msg += "sans pénalité." | ||||||
|  |         else: | ||||||
|  |             msg += "!" | ||||||
|  |         await self.ctx.send(msg) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Tirage(yaml.YAMLObject): | class Tirage(yaml.YAMLObject): | ||||||
| @@ -86,47 +281,14 @@ class Tirage(yaml.YAMLObject): | |||||||
|         assert len(teams) in (3, 4) |         assert len(teams) in (3, 4) | ||||||
|  |  | ||||||
|         self.channel: int = channel |         self.channel: int = channel | ||||||
|         self.teams = [Team(ctx, team) for team in teams] |         self.teams = [Team(team) for team in teams] | ||||||
|         self.phase = TirageOrderPhase(self, round=0) |         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) |  | ||||||
|         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): |     async def update_phase(self, ctx): | ||||||
|         if self.phase.finished(): |         if self.phase.finished(): | ||||||
|             next_class = await self.phase.next(ctx) |             next_class = await self.phase.next(ctx) | ||||||
|  |  | ||||||
|             if next_class is None: |             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) |                 await self.end(ctx) | ||||||
|             else: |             else: | ||||||
|                 # Continue on the same round. |                 # Continue on the same round. | ||||||
| @@ -163,70 +325,6 @@ class Tirage(yaml.YAMLObject): | |||||||
|         if self.channel in tirages: |         if self.channel in tirages: | ||||||
|             del tirages[self.channel] |             del tirages[self.channel] | ||||||
|  |  | ||||||
|     def records(self, round): |  | ||||||
|         """Get the strings needed for show the tirage in a list of Records""" |  | ||||||
|  |  | ||||||
|         return [ |  | ||||||
|             Record( |  | ||||||
|                 team.name, |  | ||||||
|                 (team.accepted_problems[round] or "- None")[0], |  | ||||||
|                 f"k = {team.coeff(round)} ", |  | ||||||
|             ) |  | ||||||
|             for team in in_passage_order(self.teams, round) |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
|     async def show(self, ctx): |  | ||||||
|         teams = ", ".join(team.name for team in self.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   | |  | ||||||
|     +-----+---------+---------+---------+---------+ |  | ||||||
|     | {1.name} |   Opp   |   Déf   |         |   Rap   | |  | ||||||
|     +-----+---------+---------+---------+---------+ |  | ||||||
|     | {2.name} |   Rap   |   Opp   |   Déf   |         | |  | ||||||
|     +-----+---------+---------+---------+---------+ |  | ||||||
|     | {3.name} |         |   Rap   |   Opp   |   Déf   | |  | ||||||
|     +-----+---------+---------+---------+---------+ |  | ||||||
| ```""" |  | ||||||
|  |  | ||||||
|         embed = discord.Embed( |  | ||||||
|             title=f"Résumé du tirage entre {teams}", color=EMBED_COLOR |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         for r in (0, 1): |  | ||||||
|             embed.add_field( |  | ||||||
|                 name=ROUND_NAMES[r].capitalize(), |  | ||||||
|                 value=table.format(*self.records(r)), |  | ||||||
|                 inline=False, |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|             for team in in_passage_order(self.teams, r): |  | ||||||
|                 embed.add_field( |  | ||||||
|                     name=team.name + " - " + team.accepted_problems[r], |  | ||||||
|                     value=team.details(r), |  | ||||||
|                     inline=True, |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|         await ctx.send(embed=embed) |  | ||||||
|  |  | ||||||
|     async def show_tex(self, ctx): |     async def show_tex(self, ctx): | ||||||
|         if len(self.teams) == 3: |         if len(self.teams) == 3: | ||||||
|             table = r""" |             table = r""" | ||||||
| @@ -261,51 +359,7 @@ class Tirage(yaml.YAMLObject): | |||||||
|  |  | ||||||
|  |  | ||||||
| class Phase: | 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): |  | ||||||
|         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): | class OrderPhase(Phase): | ||||||
| @@ -358,11 +412,7 @@ class OrderPhase(Phase): | |||||||
|                     re_do.extend(teams) |                     re_do.extend(teams) | ||||||
|  |  | ||||||
|             teams_str = ", ".join(team.mention for team in re_do) |             teams_str = ", ".join(team.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: |             for team in re_do: | ||||||
|                 self.set_order_for(team, None) |                 self.set_order_for(team, None) | ||||||
|             # We need to do this phase again. |             # We need to do this phase again. | ||||||
| @@ -462,32 +512,12 @@ class TiragePhase(Phase): | |||||||
|         ), "Choosing pb for a team that has a pb..." |         ), "Choosing pb for a team that has a pb..." | ||||||
|  |  | ||||||
|         if not team.drawn_problem: |         if not team.drawn_problem: | ||||||
|             if yes: |             pass | ||||||
|                 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: |         else: | ||||||
|             if yes: |             if yes: | ||||||
|                 team.accepted_problems[self.round] = team.drawn_problem |                 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: |             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) |                 team.rejected[self.round].add(team.drawn_problem) | ||||||
|                 await ctx.send(msg) |  | ||||||
|  |  | ||||||
|             team.drawn_problem = None |             team.drawn_problem = None | ||||||
|  |  | ||||||
| @@ -502,10 +532,6 @@ class TiragePhase(Phase): | |||||||
|                 i = (i + 1) % len(self.teams) |                 i = (i + 1) % len(self.teams) | ||||||
|             self.turn = i |             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: |     def finished(self) -> bool: | ||||||
|         return all(team.accepted_problems[self.round] for team in self.teams) |         return all(team.accepted_problems[self.round] for team in self.teams) | ||||||
|  |  | ||||||
| @@ -558,61 +584,12 @@ class TiragePhase(Phase): | |||||||
|         return None |         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"): | ||||||
|     def __init__(self, bot): |     def __init__(self, bot): | ||||||
|         self.bot: CustomBot = bot |         self.bot: CustomBot = bot | ||||||
|  |  | ||||||
|         # We retrieve the global variable. |         # We retrieve the global variable. | ||||||
|         # We don't want tirages to be ust an attribute |         # We don't want tirages to be just an attribute | ||||||
|         # as we want them to outlive the Cog, for instance |         # as we want them to outlive the Cog, for instance | ||||||
|         # if the cog is reloaded turing a tirage. |         # if the cog is reloaded turing a tirage. | ||||||
|         from src.tfjm_discord_bot import tirages |         from src.tfjm_discord_bot import tirages | ||||||
| @@ -773,16 +750,6 @@ class TirageCog(Cog, name="Tirages"): | |||||||
|             } |             } | ||||||
|             await channel.edit(overwrites=overwrites) |             await channel.edit(overwrites=overwrites) | ||||||
|  |  | ||||||
|         await ctx.send( |  | ||||||
|             "Nous allons commencer le tirage du premier tour. " |  | ||||||
|             "Seuls les capitaines de chaque équipe peuvent désormais écrire ici. " |  | ||||||
|             "Merci de d'envoyer seulement ce que est nécessaire et suffisant au " |  | ||||||
|             "bon déroulement du tournoi. Vous pouvez à tout moment poser toute question " |  | ||||||
|             "si quelque chose n'est pas clair ou ne va pas. \n\n" |  | ||||||
|             "Pour plus de détails sur le déroulement du tirgae au sort, le règlement " |  | ||||||
|             "est accessible sur https://tfjm.org/reglement." |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         self.tirages[channel_id] = Tirage(ctx, channel_id, teams) |         self.tirages[channel_id] = Tirage(ctx, channel_id, teams) | ||||||
|         await self.tirages[channel_id].phase.start(ctx) |         await self.tirages[channel_id].phase.start(ctx) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,11 @@ import psutil | |||||||
| from discord.ext.commands import Bot | from discord.ext.commands import Bot | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def french_join(l): | ||||||
|  |     start = ", ".join(l[:-1]) | ||||||
|  |     return f"{start} et {l[-1]}" | ||||||
|  |  | ||||||
|  |  | ||||||
| def has_role(member, role: str): | def has_role(member, role: str): | ||||||
|     """Return whether the member has a role with this name.""" |     """Return whether the member has a role with this name.""" | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user