diff --git a/orochi/bot.py b/orochi/bot.py index 6fa42c3..df0b185 100644 --- a/orochi/bot.py +++ b/orochi/bot.py @@ -1,3 +1,5 @@ +from functools import partial + import disnake from disnake import CategoryChannel, PermissionOverwrite, TextChannel from disnake.ext import commands @@ -5,7 +7,7 @@ import logging from orochi import http from orochi.config import Config -from orochi.models import Game, GameState, Player, RoundVote, Vote +from orochi.models import Game, GameState, Player, RoundVote, Vote, Round, RoundRoom, Room bot = commands.Bot(command_prefix='!') @@ -221,15 +223,16 @@ async def open(ctx: commands.Context): await ctx.reply("Les votes sont déjà ouverts.") return elif game.state == GameState.RESULTS: - await ctx.reply("Les votes viennent d'être fermés, merci de démarrer un nouveau tour avec !prepare.") + await ctx.reply("Les votes viennent d'être fermés, merci de démarrer un nouveau tour avec `!prepare`.") return # Ensure that each room is configured for room in current_round.rooms: if room is None: await ctx.reply("Les salles ne sont pas configurées.") - if len(list(room.players)) != 3: - await ctx.reply(f"La salle {room.room.value} ne contient pas trois joueurs, merci de la reconfigurer.") + if len(list(room.players)) != 3 or not all(player for player in room.players): + return await ctx.reply(f"La salle {room.room.value} ne contient pas trois joueurs, " + f"merci de finir sa configuration avec `!setup {room.room.value}`.") # Send messages to players for room in current_round.rooms: @@ -267,13 +270,15 @@ async def close(ctx: commands.Context): await ctx.reply("Les votes ne sont pas ouverts.") return + game.state = GameState.RESULTS + current_round = game.rounds[-1] for room in current_round.rooms: for vote in room.votes: if vote.vote is None: vote.vote = Vote.ALLY await ctx.send(f"L'équipe **{' et '.join(player.name for player in vote.players)}** " - f"n'a pas voté en salle {room.room.value} et s'est alliée par défaut.") + f"n'a pas voté en salle **{room.room.value}** et s'est alliée par défaut.") for player in game.players.values(): channel = bot.get_channel(player.private_channel_id) @@ -283,10 +288,79 @@ async def close(ctx: commands.Context): await channel.send("Tiens ! Vous êtes morts :)") await ctx.reply("Les votes ont bien été fermés.") - game.state = GameState.RESULTS game.save('game.save') +@bot.command(help="Préparation du tour suivant") +@commands.has_permissions(administrator=True) +async def prepare(ctx: commands.Context): + game: Game = Game.INSTANCE + if game.state != GameState.RESULTS: + await ctx.reply("Le tour actuel n'est pas terminé.") + return + + game.state = GameState.PREPARING + game.rounds.append(Round( + round=len(game.rounds) + 1, + room_a=RoundRoom( + room=Room.A, + vote1=RoundVote(), + vote2=RoundVote(), + ), + room_b=RoundRoom( + room=Room.B, + vote1=RoundVote(), + vote2=RoundVote(), + ), + room_c=RoundRoom( + room=Room.C, + vote1=RoundVote(), + vote2=RoundVote(), + ), + )) + game.save('game.save') + + await ctx.reply("Le tour suivant est en préparation. Utilisez `!setup A|B|C` pour paramétrer les salles A, B ou C. " + "Dan peut faire la même chose.") + + +@bot.command(help="Prévisualisation des combats d'un tour") +@commands.has_any_role('IA', 'Dan') +async def preview(ctx: commands.Context): + game: Game = Game.INSTANCE + current_round = game.rounds[-1] + for room in current_round.rooms: + await ctx.send(f"Dans la salle **{room.room.value}**, s'affronteront :\n" + f"- **{' et '.join(str(player or '_personne_') for player in room.vote1.players)}**\n" + f"- **{' et '.join(str(player or '_personne_') for player in room.vote2.players)}**") + + +@bot.command(help="Préparation d'une salle") +@commands.has_any_role('IA', 'Dan') +async def setup(ctx: commands.Context, room: Room): + game: Game = Game.INSTANCE + current_round = game.rounds[-1] + + if game.state != GameState.PREPARING: + return await ctx.reply("Vous ne pouvez pas préparer la salle avant le tour suivant.") + + await ctx.reply(f"Préparation de la salle {room.value}.") + + match room: + case Room.A: + round_room = current_round.room_a + case Room.B: + round_room = current_round.room_b + case _: + round_room = current_round.room_c + + view = PrepareRoomView(round_room, 0, timeout=300) + await ctx.send(f"Veuillez choisir qui s'affrontera seul dans la salle **{room.value}**.", view=view) + + if round_room.vote1.player1 is not None: + await ctx.send(f"Attention : **{round_room.vote1.player1.name}** est déjà choisie.") + + class VoteView(disnake.ui.View): @disnake.ui.button(label="S'allier", style=disnake.ButtonStyle.green) async def confirm(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction): @@ -330,6 +404,100 @@ class VoteView(disnake.ui.View): game.save('game.save') +class PrepareRoomView(disnake.ui.View): + def __init__(self, round_room: RoundRoom, player_id: int, *args, **kwargs): + super().__init__(*args, **kwargs) + assert 0 <= player_id < 3 + self.round_room = round_room + self.player_id = player_id + + for player_name in Config.PLAYERS: + async def choose_player(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction): + game: Game = Game.INSTANCE + player = game.players[button.label] + current_round = game.rounds[-1] + for room in current_round.rooms: + for vote in room.votes: + if player in vote.players: + replaced_player = None + if room == self.round_room: + match self.player_id: + case 0: + replaced_player = room.vote1.player1 + case 1: + replaced_player = room.vote2.player1 + case 2: + replaced_player = room.vote2.player2 + + if replaced_player != player: + await interaction.send( + f"Attention : **{player.name}** était déjà attribué⋅e dans la salle " + f"**{room.room.value}**. Vous devrez probablement la re-configurer.") + + if vote.player1 == player: + vote.player1 = None + elif vote.player2 == player: + vote.player2 = None + + self.clear_items() + await interaction.send("Choix bien pris en compte.") + await interaction.edit_original_message(view=self) + self.stop() + + match self.player_id: + case 0: + self.round_room.vote1.player1 = player + + view = PrepareRoomView(self.round_room, 1, timeout=300) + await interaction.send( + f"**{player.name}** se battra seul⋅e. Veuillez désormais choisir contre qui " + "il ou elle se battra.", view=view) + if self.round_room.vote2.player1 is not None: + await interaction.send( + f"Attention : **{self.round_room.vote2.player1.name}** est déjà choisi⋅e." + ) + case 1: + self.round_room.vote2.player1 = player + + view = PrepareRoomView(self.round_room, 2, timeout=300) + await interaction.send( + f"**{player.name}** se battra contre **{self.round_room.vote1.player1.name}**. " + "Veuillez désormais choisir son partenaire.", + view=view) + if self.round_room.vote2.player2 is not None: + await interaction.send( + f"Attention : **{self.round_room.vote2.player2.name}** est déjà choisi⋅e." + ) + case 2: + self.round_room.vote2.player2 = player + await interaction.send( + f"Dans la salle **{round_room.room.value}**, **{round_room.vote1.player1.name}** " + f"affrontera **{round_room.vote2.player1.name}** et **{round_room.vote2.player2.name}**.") + await interaction.send( + "Vous pouvez redéfinir la salle tant que le tour n'est pas lancé. " + "Utilisez !preview pour un récapitulatif des tours.") + + game.save('game.save') + + game: Game = Game.INSTANCE + current_round = game.rounds[-1] + for room in current_round.rooms: + for player in room.players: + if player is not None and player.name == player_name: + style = disnake.ButtonStyle.danger + break + else: + continue + break + else: + style = disnake.ButtonStyle.primary + + button = disnake.ui.Button(style=style, label=player_name) + button.callback = partial(choose_player, self, button) + button._view = self + self.add_item(button) + + def run(): config = Config.load() diff --git a/orochi/models.py b/orochi/models.py index 26ca70e..00bb82b 100644 --- a/orochi/models.py +++ b/orochi/models.py @@ -1,6 +1,5 @@ import pickle from dataclasses import dataclass, field -from datetime import datetime from enum import Enum from typing import ClassVar, Iterable, Generator @@ -62,13 +61,15 @@ class Player: return s + def __str__(self): + return self.name + @dataclass class RoundVote: - player1: Player + player1: Player | None = None player2: Player | None = None vote: Vote | None = None - timestamp: datetime | None = None @property def players(self) -> Iterable[Player]: diff --git a/orochi/templates/list.html b/orochi/templates/list.html index 24c2356..21b6b56 100644 --- a/orochi/templates/list.html +++ b/orochi/templates/list.html @@ -30,7 +30,7 @@ {% for player in game.players.values() %} {{ player.name }} - {{ player.score }} + {{ player.score }}{% if player.score <= 0 %} ☠️ {% elif player.score >= 9 %} 9️⃣ {% endif %} {% endfor %} @@ -44,7 +44,7 @@
  • État : {% if game.state.value == 0 %} - En préparation ... + En préparation ... {% elif game.state.value == 1 %} Votes en cours ... {% else %} @@ -55,7 +55,7 @@

    Récapitulatif par tour

    - {% for round in game.rounds %} + {% for round in game.rounds if admin or game.state.value > 0 or round.round < game.rounds|length %}

    Tour n°{{ round.round }}

    @@ -74,9 +74,9 @@ {% if loop.index0 == 0 %} {% endif %} - + {% if round.round != game.rounds|length or game.state.value == 2 or admin %} - + {% else %} {% endif %}
    {{ room.room.value }}{{ vote.player1.name }}{% if vote.player2 %}, {{ vote.player2.name }}{% endif %}{{ vote.player1.name|default('personne')|safe }}{% if vote.player2 %}, {{ vote.player2.name }}{% endif %}{{ vote.vote.value|default('Pas de vote') }}{{ vote.vote.value|default('Pas de vote')|safe }}Vote en cours ...