working new tirage

This commit is contained in:
ddorn 2020-05-06 19:04:36 +02:00
parent dfc8176084
commit a95c20c5b5
2 changed files with 61 additions and 191 deletions

View File

@ -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:

View File

@ -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: