Implement round preparation

This commit is contained in:
Yohann D'ANELLO 2021-11-09 00:29:07 +01:00
parent de6dc03dda
commit 388c729235
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
3 changed files with 183 additions and 14 deletions

View File

@ -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()

View File

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

View File

@ -30,7 +30,7 @@
{% for player in game.players.values() %}
<tr>
<td>{{ player.name }}</td>
<td>{{ player.score }}</td>
<td>{{ player.score }}{% if player.score <= 0 %} ☠️ {% elif player.score >= 9 %} 9⃣ {% endif %}</td>
</tr>
{% endfor %}
</tbody>
@ -44,7 +44,7 @@
<li>
<b>État :</b>
{% if game.state.value == 0 %}
<span style="color: yellow;">En préparation ...</span>
<span style="color: orange;">En préparation ...</span>
{% elif game.state.value == 1 %}
<span style="color: red;">Votes en cours ...</span>
{% else %}
@ -55,7 +55,7 @@
<h2>Récapitulatif par tour</h2>
{% for round in game.rounds %}
{% for round in game.rounds if admin or game.state.value > 0 or round.round < game.rounds|length %}
<h3>Tour n°{{ round.round }}</h3>
<table>
@ -74,9 +74,9 @@
{% if loop.index0 == 0 %}
<td rowspan="2">{{ room.room.value }}</td>
{% endif %}
<td>{{ vote.player1.name }}{% if vote.player2 %}, {{ vote.player2.name }}{% endif %}</td>
<td>{{ vote.player1.name|default('<em>personne</em>')|safe }}{% if vote.player2 %}, {{ vote.player2.name }}{% endif %}</td>
{% if round.round != game.rounds|length or game.state.value == 2 or admin %}
<td>{{ vote.vote.value|default('Pas de vote') }}</td>
<td>{{ vote.vote.value|default('<em>Pas de vote</em>')|safe }}</td>
{% else %}
<td><em>Vote en cours ...</em></td>
{% endif %}