diff --git a/tfjm-discord-bot.py b/tfjm-discord-bot.py index 45dfa8e..96b66be 100644 --- a/tfjm-discord-bot.py +++ b/tfjm-discord-bot.py @@ -6,7 +6,7 @@ import traceback from collections import defaultdict from operator import attrgetter from time import sleep -from typing import Dict +from typing import Dict, Type import discord from discord.ext import commands @@ -31,6 +31,8 @@ with open("problems") as f: PROBLEMS = f.read().splitlines() MAX_REFUSE = len(PROBLEMS) - 5 +ROUND_NAMES = ["premier tour", "deuxième tour"] + class TfjmError(Exception): def __init__(self, msg): @@ -44,12 +46,12 @@ class Team: def __init__(self, ctx, name): self.name = name self.role = get(ctx.guild.roles, name=name) - self.tirage_order = None - self.passage_order = None + self.tirage_order = [None, None] + self.passage_order = [None, None] - self.accepted_problem = None + self.accepted_problems = [None, None] self.drawn_problem = None # Waiting to be accepted or refused - self.rejected = set() + self.rejected = [set(), set()] @property def mention(self): @@ -62,7 +64,7 @@ class Tirage: self.channel = channel self.teams = [Team(ctx, team) for team in teams] - self.phase = TirageOrderPhase(self) + self.phase = TirageOrderPhase(self, round=0) def team_for(self, author): for team in self.teams: @@ -89,21 +91,34 @@ class Tirage: async def update_phase(self, ctx): if self.phase.finished(): - self.phase = await self.phase.next(ctx) + next_class = await self.phase.next(ctx) - if self.phase is None: + if next_class is None: await ctx.send( "Le tirage est fini ! Bonne chance à tous pour la suite !" ) del tirages[self.channel] else: + # Continue on the same round. + # If a Phase wants to change the round + # it needs to change its own round. + self.phase = next_class(self, self.phase.round) await self.phase.start(ctx) class Phase: NEXT = None - def __init__(self, tirage): + 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 async def fais_pas_chier(self, ctx): @@ -140,24 +155,22 @@ class Phase: async def start(self, ctx): pass - async def next(self, ctx: Context) -> "Phase": - if self.NEXT is None: - return None - return self.NEXT(self.tirage) + async def next(self, ctx: Context) -> "Type[Phase]": + return self.NEXT class OrderPhase(Phase): - def __init__(self, tirage, name, order_name, reverse=False): - super().__init__(tirage) + def __init__(self, tirage, round, name, order_name, reverse=False): + super().__init__(tirage, round) self.name = name self.reverse = reverse self.order_name = order_name def order_for(self, team): - return getattr(team, self.order_name) + return getattr(team, self.order_name)[self.round] def set_order_for(self, team, order): - setattr(team, self.order_name, order) + getattr(team, self.order_name)[self.round] = order async def dice(self, ctx, author, dice): team = self.team_for(author) @@ -171,22 +184,20 @@ class OrderPhase(Phase): def finished(self) -> bool: return all(self.order_for(team) is not None for team in self.teams) - async def next(self, ctx) -> "Phase": + async def next(self, ctx) -> "Type[Phase]": orders = [self.order_for(team) for team in self.teams] if len(set(orders)) == len(orders): # All dice are different: good self.teams.sort(key=self.order_for, reverse=self.reverse) await ctx.send( - f"L'ordre {self.name} pour ce tour est donc :\n" + f"L'ordre {self.name} pour ce 9 est donc :\n" " - " + "\n - ".join( f"{team.role.mention} ({self.order_for(team)})" for team in self.teams ) ) - if self.NEXT is not None: - return self.NEXT(self.tirage) - return None + return self.NEXT else: # Find dice that are the same count = defaultdict(list) @@ -206,14 +217,29 @@ class OrderPhase(Phase): ) for team in re_do: self.set_order_for(team, None) - return self + # We need to do this phase again. + return self.__class__ + + +class ShowPhase(Phase): + """Phase where the bot resumes all that happened.""" + + NEXT = None class TiragePhase(Phase): """The phase where captains accept or refuse random problems.""" - def __init__(self, tirage): - super().__init__(tirage) + NEXT = ShowPhase + + 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 @@ -221,7 +247,7 @@ class TiragePhase(Phase): return self.teams[self.turn] def available(self, problem): - return all(team.accepted_problem != problem for team in self.teams) + return all(team.accepted_problems[self.round] != problem for team in self.teams) async def choose_problem(self, ctx: Context, author, problem): team = self.current_team @@ -232,7 +258,9 @@ class TiragePhase(Phase): ) return - assert team.accepted_problem is None, "Choosing pb for a team that has a pb..." + assert ( + team.accepted_problems[self.round] is None + ), "Choosing pb for a team that has a pb..." if team.drawn_problem: await ctx.send( @@ -244,7 +272,13 @@ class TiragePhase(Phase): f"Malheureusement, **{problem}** à déjà été choisi, " f"vous pouvez tirer un nouveau problème." ) - elif problem in team.rejected: + 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}**, " @@ -254,7 +288,7 @@ class TiragePhase(Phase): ) else: team.drawn_problem = problem - if len(team.rejected) >= MAX_REFUSE: + 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 " @@ -264,7 +298,7 @@ class TiragePhase(Phase): else: await ctx.send( f"Vous pouvez accepter (`!oui`) ou refuser (`!non`) **{problem}**. " - f"Il reste {MAX_REFUSE - len(team.rejected)} refus sans pénalité " + f"Il reste {MAX_REFUSE - len(team.rejected[self.round])} refus sans pénalité " f"pour {team.mention}." ) @@ -278,7 +312,9 @@ class TiragePhase(Phase): ) return - assert team.accepted_problem is None, "Choosing pb for a team that has a pb..." + assert ( + team.accepted_problems[self.round] is None + ), "Choosing pb for a team that has a pb..." if not team.drawn_problem: if yes: @@ -293,20 +329,20 @@ class TiragePhase(Phase): ) else: if yes: - team.accepted_problem = team.drawn_problem + team.accepted_problems[self.round] = team.drawn_problem await ctx.send( f"L'équipe {team.mention} a accepté " - f"**{team.accepted_problem}** ! Les autres équipes " + f"**{team.accepted_problems[self.round]}** ! Les autres équipes " f"ne peuvent plus l'accepter." ) else: - if team.drawn_problem in team.rejected: + if team.drawn_problem in team.rejected[self.round]: await ctx.send( f"{team.mention} a refusé **{team.drawn_problem}** " f"sans pénalité." ) else: - team.rejected.add(team.drawn_problem) + team.rejected[self.round].add(team.drawn_problem) await ctx.send(f"{team.mention} a refusé **{team.drawn_problem}**!") team.drawn_problem = None @@ -318,7 +354,7 @@ class TiragePhase(Phase): # Find next team that needs to draw. i = (self.turn + 1) % len(self.teams) - while self.teams[i].accepted_problem: + while self.teams[i].accepted_problems[self.round]: i = (i + 1) % len(self.teams) self.turn = i @@ -327,13 +363,13 @@ class TiragePhase(Phase): ) def finished(self) -> bool: - return all(team.accepted_problem for team in self.teams) + 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=attrgetter("tirage_order")) + self.teams.sort(key=lambda team: team.tirage_order[self.round]) - async with ctx.typing(): + if self.round == 0: sleep(0.5) await ctx.send("Passons au tirage des problèmes !") sleep(0.5) @@ -354,21 +390,37 @@ class TiragePhase(Phase): f"Un problème déjà rejeté ne compte pas deux fois." ) await ctx.send("Bonne chance à tous ! C'est parti...") - sleep(1.5) + else: + # Second round + sleep(0.5) await ctx.send( - f"{self.current_team.mention} à toi l'honneur! " - f"Lance `!random-problem` quand tu veux." + "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." ) + 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 ShowPhase + class PassageOrderPhase(OrderPhase): """The phase to determine the chicken's order.""" NEXT = TiragePhase - def __init__(self, tirage): - super().__init__(tirage, "de passage", "passage_order", True) + def __init__(self, tirage, round=0): + super().__init__(tirage, round, "de passage", "passage_order", True) async def start(self, ctx): await ctx.send( @@ -390,16 +442,17 @@ class TirageOrderPhase(OrderPhase): NEXT = PassageOrderPhase - def __init__(self, tirage): - super().__init__(tirage, "des tirages", "tirage_order", False) + def __init__(self, tirage, round=0): + super().__init__(tirage, round, "des tirages", "tirage_order", False) async def start(self, ctx): captain = get(ctx.guild.roles, name=CAPTAIN_ROLE) 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, " - "puis l'ordre de passage lors du tour." + "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." ) sleep(0.5) await ctx.send( @@ -454,8 +507,8 @@ async def draw_skip(ctx, *teams): tirage.phase = TiragePhase(tirage) for i, team in enumerate(tirage.teams): - team.tirage_order = i - team.passage_order = i + team.tirage_order = [i, None] + team.passage_order = [i, None] await ctx.send(f"Skipping to {tirage.phase.__class__.__name__}.") await tirage.phase.start(ctx)