✨ working new tirage
This commit is contained in:
parent
dfc8176084
commit
a95c20c5b5
|
@ -3,12 +3,14 @@ import random
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from pathlib import Path
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import Type, Union, Dict, List
|
from typing import Type, Union, Dict, List
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
import yaml
|
||||||
|
|
||||||
from src.constants import *
|
from src.constants import *
|
||||||
|
|
||||||
|
@ -70,7 +72,9 @@ class Team:
|
||||||
# """
|
# """
|
||||||
|
|
||||||
|
|
||||||
class Poule:
|
class Poule(yaml.YAMLObject):
|
||||||
|
yaml_tag = "Poule"
|
||||||
|
|
||||||
def __init__(self, poule, rnd):
|
def __init__(self, poule, rnd):
|
||||||
self.poule = poule
|
self.poule = poule
|
||||||
self.rnd = rnd
|
self.rnd = rnd
|
||||||
|
@ -79,15 +83,17 @@ class Poule:
|
||||||
return f"{self.poule}{self.rnd + 1}"
|
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)):
|
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.teams: Dict[str, Team] = {t.name: Team(t) for t in teams}
|
||||||
self.format = fmt
|
self.format = fmt
|
||||||
self.queue = asyncio.Queue()
|
self.queue = asyncio.Queue()
|
||||||
self.poules: Dict[Poule, List[str]] = {}
|
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):
|
async def event(self, event: Event):
|
||||||
event.set()
|
event.set()
|
||||||
|
@ -153,8 +159,9 @@ class BaseTirage:
|
||||||
while None in dices.values():
|
while None in dices.values():
|
||||||
event = await self.next(int)
|
event = await self.next(int)
|
||||||
|
|
||||||
# TODO: avoid KeyError
|
if event.team not in dices:
|
||||||
if dices[event.team] is None:
|
await self.warn_wrong_team(None, event.team)
|
||||||
|
elif dices[event.team] is None:
|
||||||
dices[event.team] = event.value
|
dices[event.team] = event.value
|
||||||
await self.info_dice(event.team, event.value)
|
await self.info_dice(event.team, event.value)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -17,20 +17,16 @@ 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, Event
|
from src.base_tirage import BaseTirage, Event, Poule
|
||||||
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__ = ["TirageCog"]
|
||||||
|
|
||||||
from src.utils import send_and_bin, french_join
|
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"])
|
Record = namedtuple("Record", ["name", "pb", "penalite"])
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,6 +75,36 @@ class DiscordTirage(BaseTirage):
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.captain_mention = get(ctx.guild.roles, name=Role.CAPTAIN).mention
|
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):
|
def team_for(self, author):
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
if get(author.roles, name=team):
|
if get(author.roles, name=team):
|
||||||
|
@ -256,8 +282,7 @@ class DiscordTirage(BaseTirage):
|
||||||
| {1.name} | Opp | Déf | Rap |
|
| {1.name} | Opp | Déf | Rap |
|
||||||
+-----+---------+---------+---------+
|
+-----+---------+---------+---------+
|
||||||
| {2.name} | Rap | Opp | Déf |
|
| {2.name} | Rap | Opp | Déf |
|
||||||
+-----+---------+---------+---------+
|
+-----+---------+---------+---------+```"""
|
||||||
```"""
|
|
||||||
else:
|
else:
|
||||||
table = """```
|
table = """```
|
||||||
+-----+---------+---------+---------+---------+
|
+-----+---------+---------+---------+---------+
|
||||||
|
@ -271,8 +296,7 @@ class DiscordTirage(BaseTirage):
|
||||||
| {2.name} | Rap | Opp | Déf | |
|
| {2.name} | Rap | Opp | Déf | |
|
||||||
+-----+---------+---------+---------+---------+
|
+-----+---------+---------+---------+---------+
|
||||||
| {3.name} | | Rap | Opp | Déf |
|
| {3.name} | | Rap | Opp | Déf |
|
||||||
+-----+---------+---------+---------+---------+
|
+-----+---------+---------+---------+---------+```"""
|
||||||
```"""
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title=f"Résumé du tirage entre {french_join([t.name for t in teams])}",
|
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(
|
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)
|
await self.ctx.send(embed=embed)
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
@safe
|
@safe
|
||||||
@send_all
|
@send_all
|
||||||
async def info_start(self):
|
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 "
|
"Vous pouvez désormais trouver les problèmes que vous devrez opposer ou rapporter "
|
||||||
"sur la page de votre équipe."
|
"sur la page de votre équipe."
|
||||||
)
|
)
|
||||||
# TODO: Save it
|
|
||||||
# TODO: make them available with the api
|
# TODO: make them available with the api
|
||||||
|
|
||||||
@safe
|
@safe
|
||||||
|
@ -380,170 +405,10 @@ class DiscordTirage(BaseTirage):
|
||||||
msg += "!"
|
msg += "!"
|
||||||
await self.ctx.send(msg)
|
await self.ctx.send(msg)
|
||||||
|
|
||||||
|
async def show(self, ctx):
|
||||||
class TiragePhase:
|
self.ctx = ctx
|
||||||
"""The phase where captains accept or refuse random problems."""
|
for poule in self.poules:
|
||||||
|
await self.annonce_poule(poule)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class TirageCog(Cog, name="Tirages"):
|
class TirageCog(Cog, name="Tirages"):
|
||||||
|
@ -715,6 +580,7 @@ class TirageCog(Cog, name="Tirages"):
|
||||||
# Here all data should be valid
|
# Here all data should be valid
|
||||||
|
|
||||||
self.tirages[channel_id] = DiscordTirage(ctx, *teams, fmt=fmt)
|
self.tirages[channel_id] = DiscordTirage(ctx, *teams, fmt=fmt)
|
||||||
|
|
||||||
await self.tirages[channel_id].run()
|
await self.tirages[channel_id].run()
|
||||||
|
|
||||||
if self.tirages[channel_id]:
|
if self.tirages[channel_id]:
|
||||||
|
@ -762,13 +628,12 @@ class TirageCog(Cog, name="Tirages"):
|
||||||
# await tirage.update_phase(ctx)
|
# await tirage.update_phase(ctx)
|
||||||
|
|
||||||
def get_tirages(self) -> Dict[int, BaseTirage]:
|
def get_tirages(self) -> Dict[int, BaseTirage]:
|
||||||
if not File.TIRAGES.exists():
|
return DiscordTirage.load_all()
|
||||||
return {}
|
|
||||||
|
|
||||||
with open(File.TIRAGES) as f:
|
def save_tirages(self, tirages):
|
||||||
tirages = yaml.load(f)
|
File.TIRAGES.touch()
|
||||||
|
with open(File.TIRAGES, "w") as f:
|
||||||
return tirages
|
yaml.dump(tirages, f)
|
||||||
|
|
||||||
@draw_group.command(name="show")
|
@draw_group.command(name="show")
|
||||||
async def show_cmd(self, ctx: Context, tirage_id: str = "all"):
|
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
|
`!draw show 42` - Affiche le tirage n°42
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return
|
|
||||||
tirages = self.get_tirages()
|
tirages = self.get_tirages()
|
||||||
|
|
||||||
if not tirages:
|
if not tirages:
|
||||||
|
@ -793,8 +657,7 @@ class TirageCog(Cog, name="Tirages"):
|
||||||
"Vous pouvez en consulter un en particulier avec `!draw show ID`."
|
"Vous pouvez en consulter un en particulier avec `!draw show ID`."
|
||||||
)
|
)
|
||||||
msg = "\n".join(
|
msg = "\n".join(
|
||||||
f"`{key}`: {', '.join(team.name for team in tirage.teams)}"
|
f"`{key}`: {', '.join(tirage.teams)}" for key, tirage in tirages.items()
|
||||||
for key, tirage in tirages.items()
|
|
||||||
)
|
)
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue