easterobot 1.3.2__py3-none-any.whl → 1.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. easterobot/bot.py +14 -1
  2. easterobot/casino/__init__.py +1 -0
  3. easterobot/casino/roulette.py +269 -0
  4. easterobot/commands/__init__.py +2 -0
  5. easterobot/commands/game.py +127 -118
  6. easterobot/commands/reset.py +11 -14
  7. easterobot/commands/roulette.py +34 -0
  8. easterobot/commands/top.py +73 -65
  9. easterobot/config.py +35 -8
  10. easterobot/games/{connect.py → connect4.py} +25 -28
  11. easterobot/games/game.py +126 -54
  12. easterobot/games/rock_paper_scissor.py +33 -30
  13. easterobot/games/skyjo.py +805 -0
  14. easterobot/games/tic_tac_toe.py +19 -18
  15. easterobot/hunts/hunt.py +49 -18
  16. easterobot/hunts/rank.py +24 -2
  17. easterobot/locker.py +180 -0
  18. easterobot/models.py +9 -0
  19. easterobot/resources/config.example.yml +8 -2
  20. easterobot/resources/credits.txt +2 -0
  21. easterobot/resources/emotes/placements/s1.png +0 -0
  22. easterobot/resources/emotes/placements/s10.png +0 -0
  23. easterobot/resources/emotes/placements/s11.png +0 -0
  24. easterobot/resources/emotes/placements/s12.png +0 -0
  25. easterobot/resources/emotes/placements/s2.png +0 -0
  26. easterobot/resources/emotes/placements/s3.png +0 -0
  27. easterobot/resources/emotes/placements/s4.png +0 -0
  28. easterobot/resources/emotes/placements/s5.png +0 -0
  29. easterobot/resources/emotes/placements/s6.png +0 -0
  30. easterobot/resources/emotes/placements/s7.png +0 -0
  31. easterobot/resources/emotes/placements/s8.png +0 -0
  32. easterobot/resources/emotes/placements/s9.png +0 -0
  33. easterobot/resources/emotes/placements/sA.png +0 -0
  34. easterobot/resources/emotes/placements/sB.png +0 -0
  35. easterobot/resources/emotes/placements/sC.png +0 -0
  36. easterobot/resources/emotes/placements/sD.png +0 -0
  37. easterobot/resources/emotes/placements/sE.png +0 -0
  38. easterobot/resources/emotes/placements/sF.png +0 -0
  39. easterobot/resources/emotes/placements/sG.png +0 -0
  40. easterobot/resources/emotes/placements/sH.png +0 -0
  41. easterobot/resources/emotes/placements/sI.png +0 -0
  42. easterobot/resources/emotes/placements/sJ.png +0 -0
  43. easterobot/resources/emotes/placements/sK.png +0 -0
  44. easterobot/resources/emotes/placements/sL.png +0 -0
  45. easterobot/resources/emotes/placements/sM.png +0 -0
  46. easterobot/resources/emotes/placements/sN.png +0 -0
  47. easterobot/resources/emotes/placements/sO.png +0 -0
  48. easterobot/resources/emotes/placements/sP.png +0 -0
  49. easterobot/resources/emotes/placements/sQ.png +0 -0
  50. easterobot/resources/emotes/placements/sR.png +0 -0
  51. easterobot/resources/emotes/placements/sS.png +0 -0
  52. easterobot/resources/emotes/placements/sT.png +0 -0
  53. easterobot/resources/emotes/placements/sU.png +0 -0
  54. easterobot/resources/emotes/placements/sV.png +0 -0
  55. easterobot/resources/emotes/placements/sW.png +0 -0
  56. easterobot/resources/emotes/placements/sX.png +0 -0
  57. easterobot/resources/emotes/placements/sY.png +0 -0
  58. easterobot/resources/emotes/placements/sZ.png +0 -0
  59. easterobot/resources/emotes/placements/s_.png +0 -0
  60. easterobot/resources/emotes/skyjo/skyjo_back.png +0 -0
  61. easterobot/resources/emotes/skyjo/skyjo_m1.png +0 -0
  62. easterobot/resources/emotes/skyjo/skyjo_m2.png +0 -0
  63. easterobot/resources/emotes/skyjo/skyjo_p0.png +0 -0
  64. easterobot/resources/emotes/skyjo/skyjo_p1.png +0 -0
  65. easterobot/resources/emotes/skyjo/skyjo_p10.png +0 -0
  66. easterobot/resources/emotes/skyjo/skyjo_p11.png +0 -0
  67. easterobot/resources/emotes/skyjo/skyjo_p12.png +0 -0
  68. easterobot/resources/emotes/skyjo/skyjo_p2.png +0 -0
  69. easterobot/resources/emotes/skyjo/skyjo_p3.png +0 -0
  70. easterobot/resources/emotes/skyjo/skyjo_p4.png +0 -0
  71. easterobot/resources/emotes/skyjo/skyjo_p5.png +0 -0
  72. easterobot/resources/emotes/skyjo/skyjo_p6.png +0 -0
  73. easterobot/resources/emotes/skyjo/skyjo_p7.png +0 -0
  74. easterobot/resources/emotes/skyjo/skyjo_p8.png +0 -0
  75. easterobot/resources/emotes/skyjo/skyjo_p9.png +0 -0
  76. {easterobot-1.3.2.dist-info → easterobot-1.5.1.dist-info}/METADATA +1 -1
  77. easterobot-1.5.1.dist-info/RECORD +130 -0
  78. easterobot-1.3.2.dist-info/RECORD +0 -70
  79. {easterobot-1.3.2.dist-info → easterobot-1.5.1.dist-info}/WHEEL +0 -0
  80. {easterobot-1.3.2.dist-info → easterobot-1.5.1.dist-info}/entry_points.txt +0 -0
  81. {easterobot-1.3.2.dist-info → easterobot-1.5.1.dist-info}/licenses/LICENSE +0 -0
easterobot/games/game.py CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  import asyncio
4
4
  import logging
5
- from collections.abc import Coroutine
5
+ from collections.abc import Coroutine, Iterable
6
6
  from dataclasses import dataclass
7
- from typing import Any, Callable, Optional
7
+ from typing import TYPE_CHECKING, Any, Callable, Optional
8
8
  from uuid import uuid4
9
9
 
10
10
  import discord
@@ -13,7 +13,7 @@ from discord.message import convert_emoji_reaction
13
13
 
14
14
  from easterobot.bot import Easterobot
15
15
  from easterobot.commands.base import Context, Interaction, InteractionChannel
16
- from easterobot.config import RAND
16
+ from easterobot.config import RAND, agree
17
17
  from easterobot.utils import in_seconds
18
18
 
19
19
  logger = logging.getLogger(__name__)
@@ -38,22 +38,35 @@ class InvalidPlayerError(GameError):
38
38
  )
39
39
 
40
40
 
41
- @dataclass
41
+ @dataclass(frozen=True)
42
42
  class Player:
43
43
  member: discord.Member
44
44
  number: int
45
45
 
46
46
 
47
47
  class Game:
48
- bot: Easterobot
49
-
50
- def __init__(self, message: discord.Message) -> None:
48
+ def __init__(
49
+ self,
50
+ bot: Easterobot,
51
+ message: discord.Message,
52
+ *members: discord.Member,
53
+ ) -> None:
51
54
  """Instantiate Game."""
52
55
  self.id = uuid4()
56
+ if self.minimum_player() > len(members):
57
+ error_message = "Not enough players."
58
+ raise InvalidActionError(error_message)
59
+ if self.maximum_player() < len(members):
60
+ error_message = "Too many players."
61
+ raise InvalidActionError(error_message)
62
+ self.bot = bot
63
+ self.players = [Player(member, i) for i, member in enumerate(members)]
53
64
  self.message = message
54
65
  self.terminate = False
55
- self.winner: Optional[discord.Member] = None
66
+ self.winner: Optional[Player] = None
56
67
  self.lock = asyncio.Lock()
68
+
69
+ # Manager
57
70
  self._cleanup: Optional[AsyncCallback] = None
58
71
  self._completion: Optional[AsyncCallback] = None
59
72
  self._end_event = asyncio.Event()
@@ -62,12 +75,23 @@ class Game:
62
75
  self._reset_countdown_event = asyncio.Event()
63
76
  self._timeout_task: Optional[asyncio.Task[None]] = None
64
77
  self._timeout_lock: asyncio.Lock = asyncio.Lock()
78
+ self.in_seconds: Optional[str] = None
65
79
 
66
80
  async def set_completion(self, callback: AsyncCallback) -> None:
67
81
  """Get the current state for a player."""
68
82
  self._completion = callback
69
83
 
70
- async def wait_winner(self) -> Optional[discord.Member]:
84
+ @classmethod
85
+ def minimum_player(cls) -> int:
86
+ """Get the minimum player number."""
87
+ return 2
88
+
89
+ @classmethod
90
+ def maximum_player(cls) -> int:
91
+ """Get the maximum player number."""
92
+ return 2
93
+
94
+ async def wait_winner(self) -> Optional[Player]:
71
95
  """Wait the end of the game."""
72
96
  await self._end_event.wait()
73
97
  return self.winner
@@ -85,7 +109,7 @@ class Game:
85
109
  async def on_timeout(self) -> None:
86
110
  """Can when game timeout."""
87
111
 
88
- async def set_winner(self, winner: Optional[discord.Member]) -> None:
112
+ async def set_winner(self, winner: Optional[Player]) -> None:
89
113
  """Remove the game from the manager."""
90
114
  self.terminate = True
91
115
  self.winner = winner
@@ -98,6 +122,11 @@ class Game:
98
122
  async def start_timer(self, seconds: float) -> str:
99
123
  """Start the timer for turn."""
100
124
  async with self._timeout_lock:
125
+ if self._timeout_task and (
126
+ self._timeout_task.done() or self._timeout_task.cancelled()
127
+ ):
128
+ error_message = "Timer was already started"
129
+ raise RuntimeError(error_message)
101
130
  logger.info(
102
131
  "Start timer of %s seconds for %s",
103
132
  seconds,
@@ -106,7 +135,8 @@ class Game:
106
135
  self._timeout_task = asyncio.create_task(
107
136
  self._timeout_worker(seconds)
108
137
  )
109
- return in_seconds(seconds)
138
+ self.in_seconds = in_seconds(seconds)
139
+ return self.in_seconds
110
140
 
111
141
  async def stop_timer(self) -> None:
112
142
  """Stop the timer and wait it end."""
@@ -119,8 +149,8 @@ class Game:
119
149
  ):
120
150
  self._reset_countdown_event.set()
121
151
  await self._timeout_task
122
- self._timeout_task = None
123
- self._reset_countdown_event = asyncio.Event()
152
+ self._timeout_task = None
153
+ self._reset_countdown_event = asyncio.Event()
124
154
  logger.info("Timer stopped for %s", self)
125
155
 
126
156
  async def _timeout_worker(self, seconds: float) -> None:
@@ -130,9 +160,12 @@ class Game:
130
160
  await asyncio.wait_for(event.wait(), timeout=seconds)
131
161
  except asyncio.TimeoutError:
132
162
  if not event.is_set():
163
+ logger.info("Acquire lock for %s", self)
133
164
  async with self.lock:
134
165
  logger.info("Timeout for %s", self)
135
- await self.on_timeout()
166
+ # TODO(dashstrom): Handle the task at end !
167
+ asyncio.create_task(self.on_timeout()) # noqa: RUF006
168
+ logger.info("Terminate worker for %s ", self)
136
169
 
137
170
  def __repr__(self) -> str:
138
171
  """Get game representation."""
@@ -160,20 +193,29 @@ class GameCog(commands.Cog):
160
193
  reference: discord.Message,
161
194
  user1: discord.Member,
162
195
  user2: discord.Member,
163
- ) -> Optional[discord.Member]:
196
+ ) -> Optional[Player]:
164
197
  """Start a dual between two players."""
165
- from easterobot.games.connect import Connect4
198
+ from easterobot.games.connect4 import Connect4
166
199
  from easterobot.games.rock_paper_scissor import RockPaperScissor
200
+ from easterobot.games.skyjo import Skyjo
167
201
  from easterobot.games.tic_tac_toe import TicTacToe
168
202
 
169
- cls = RAND.choice([Connect4, TicTacToe, RockPaperScissor])
203
+ cls = RAND.choice([Connect4, TicTacToe, RockPaperScissor, Skyjo])
170
204
  msg = await channel.send(
171
205
  f"{user1.mention} et {user2.mention} "
172
- f"vont s'affronter {in_seconds(121)} ...",
206
+ f"vont s'affronter {in_seconds(300)} ...",
173
207
  reference=reference,
174
208
  )
175
- await asyncio.sleep(121)
176
- game: Game = cls(user1, user2, msg) # type: ignore[operator]
209
+ await asyncio.sleep(270)
210
+ await msg.reply(
211
+ content=(
212
+ f"{user1.mention} et {user2.mention} vont commencer le duel "
213
+ f"{in_seconds(30)}"
214
+ ),
215
+ delete_after=30,
216
+ )
217
+ await asyncio.sleep(30)
218
+ game: Game = cls(self.bot, msg, user1, user2)
177
219
  await self.run(game)
178
220
  return await game.wait_winner()
179
221
 
@@ -196,18 +238,19 @@ class GameCog(commands.Cog):
196
238
 
197
239
  self._games[message_id] = game
198
240
  game._cleanup = _cleanup # noqa: SLF001
199
- game.bot = self.bot
200
241
  await game.on_start()
201
242
 
202
- async def ask_dual(
243
+ async def ask_dual( # noqa: C901, PLR0915
203
244
  self,
204
245
  ctx: Context,
205
- member: discord.Member,
246
+ members: Iterable[discord.Member],
206
247
  bet: int,
207
248
  ) -> Optional[discord.Message]:
208
249
  """Send basic message for initialization."""
209
- future: asyncio.Future[Optional[bool]] = asyncio.Future()
210
- accept: Optional[bool] = None
250
+ event = asyncio.Event()
251
+ pending_members = list(members)
252
+ accepted_members: list[discord.Member] = [ctx.user]
253
+ cancel_by: Optional[discord.Member] = None
211
254
 
212
255
  view = discord.ui.View()
213
256
  yes_btn: Button = discord.ui.Button(
@@ -218,26 +261,47 @@ class GameCog(commands.Cog):
218
261
  )
219
262
 
220
263
  async def yes(interaction: Interaction) -> Any:
221
- await interaction.response.defer()
222
- if not future.done() and interaction.user.id == member.id:
223
- future.set_result(True)
264
+ nonlocal cancel_by
265
+ if TYPE_CHECKING:
266
+ assert isinstance(interaction.user, discord.Member)
267
+ if not event.is_set() and any(
268
+ interaction.user.id == m.id for m in pending_members
269
+ ):
270
+ await interaction.response.send_message(
271
+ "Vous avez accepté le duel !",
272
+ ephemeral=True,
273
+ )
274
+ accepted_members.append(interaction.user)
275
+ pending_members.remove(interaction.user)
276
+ if not pending_members:
277
+ event.set()
278
+ else:
279
+ await interaction.response.defer()
224
280
 
225
281
  async def no(interaction: Interaction) -> Any:
282
+ nonlocal cancel_by
283
+ if TYPE_CHECKING:
284
+ assert isinstance(interaction.user, discord.Member)
226
285
  await interaction.response.defer()
227
- if not future.done():
228
- if interaction.user.id == member.id:
229
- future.set_result(False)
230
- if interaction.user.id == ctx.user.id:
231
- future.set_result(None)
286
+ if not event.is_set():
287
+ if interaction.user in pending_members:
288
+ pending_members.remove(interaction.user)
289
+ cancel_by = interaction.user
290
+ event.set()
291
+ elif interaction.user in accepted_members:
292
+ accepted_members.remove(interaction.user)
293
+ cancel_by = interaction.user
294
+ event.set()
232
295
 
233
296
  yes_btn.callback = yes # type: ignore[method-assign,assignment]
234
297
  no_btn.callback = no # type: ignore[method-assign,assignment]
235
298
  view.add_item(yes_btn)
236
299
  view.add_item(no_btn)
237
- seconds = 600
300
+ seconds = 300
301
+ mention = " ".join(m.mention for m in pending_members)
238
302
  result = await ctx.response.send_message(
239
- f"{member.mention}, {ctx.user.mention} "
240
- f"vous demande en duel pour `{bet}` œufs ⚔️"
303
+ f"{mention}, "
304
+ f"{ctx.user.mention} vous demande en duel pour `{bet}` œufs ⚔️"
241
305
  f"\nVous devez repondre {in_seconds(seconds)} !",
242
306
  view=view,
243
307
  )
@@ -246,32 +310,34 @@ class GameCog(commands.Cog):
246
310
  error_message = f"Invalid kind of message: {message!r}"
247
311
  raise TypeError(error_message)
248
312
  try:
249
- accept = await asyncio.wait_for(future, timeout=seconds)
313
+ await asyncio.wait_for(event.wait(), timeout=seconds)
250
314
  except asyncio.TimeoutError:
315
+ mention = " ".join(m.mention for m in accepted_members)
251
316
  await message.edit(
252
317
  content=(
253
- f"{ctx.user.mention}, "
254
- f"{member.mention} n'a pas accepté le duel 🛡️"
255
- f"\n-# Ce message disparaîtra {in_seconds(30)}"
318
+ f"{mention}, "
319
+ f"{' '.join(m.mention for m in pending_members)} "
320
+ + agree("n'a pas", "n'ont pas", len(pending_members) - 1)
321
+ + " accepté le duel 🛡️"
322
+ + "\n-# Ce message disparaîtra {in_seconds(30)}"
256
323
  ),
257
324
  delete_after=30,
258
325
  view=None,
259
326
  )
260
327
  return None
261
- if accept is None:
262
- await message.edit(
263
- content=(
264
- f"{member.mention}, {ctx.user.mention} a annulé le duel 🛡️"
265
- f"\n-# Ce message disparaîtra {in_seconds(30)}"
266
- ),
267
- delete_after=30,
268
- view=None,
328
+ if cancel_by:
329
+ mention = " ".join(
330
+ m.mention
331
+ for group in (
332
+ accepted_members,
333
+ pending_members,
334
+ )
335
+ for m in group
269
336
  )
270
- return None
271
- if not accept:
337
+ word = "refusé" if cancel_by == ctx.user else "annulé"
272
338
  await message.edit(
273
339
  content=(
274
- f"{ctx.user.mention}, {member.mention} a refusé le duel 🛡️"
340
+ f"{mention}, {cancel_by.mention} a {word} le duel 🛡️"
275
341
  f"\n-# Ce message disparaîtra {in_seconds(30)}"
276
342
  ),
277
343
  delete_after=30,
@@ -281,12 +347,18 @@ class GameCog(commands.Cog):
281
347
  if not isinstance(result.resource, discord.Message):
282
348
  error_message = f"Invalid kind of message: {result.resource!r}"
283
349
  raise TypeError(error_message)
350
+ mention = " ".join(
351
+ m.mention for m in accepted_members if m != ctx.user
352
+ )
284
353
  await result.resource.reply(
285
- f"{ctx.user.mention}, {member.mention} a accepté le duel ⚔️"
286
- f"\n-# Ce message disparaîtra {in_seconds(30)}",
354
+ f"{ctx.user.mention}, {mention} "
355
+ + agree("a", "ont", len(accepted_members) - 1)
356
+ + " accepté le duel ⚔️"
357
+ + f"\n-# Début du duel {in_seconds(30)}",
287
358
  delete_after=30,
288
359
  )
289
- return result.resource
360
+ await asyncio.sleep(30)
361
+ return await ctx.channel.fetch_message(result.resource.id)
290
362
 
291
363
  @commands.Cog.listener()
292
364
  async def on_raw_reaction_add(
@@ -7,6 +7,7 @@ from typing import Optional
7
7
  import discord
8
8
  from typing_extensions import override
9
9
 
10
+ from easterobot.bot import Easterobot
10
11
  from easterobot.commands.base import Interaction
11
12
  from easterobot.games.game import Button, Game
12
13
 
@@ -21,9 +22,9 @@ class RockPaperScissor(Game):
21
22
 
22
23
  def __init__(
23
24
  self,
24
- player1: discord.Member,
25
- player2: discord.Member,
25
+ bot: Easterobot,
26
26
  message: discord.Message,
27
+ *members: discord.Member,
27
28
  win_count: int = 3,
28
29
  max_turn: int = 10,
29
30
  ) -> None:
@@ -31,12 +32,10 @@ class RockPaperScissor(Game):
31
32
  self.timeout = False
32
33
  self.max_turn = max_turn
33
34
  self.win_count = win_count
34
- self.player1 = player1
35
- self.player2 = player2
36
35
  self.play1: Optional[str] = None
37
36
  self.play2: Optional[str] = None
38
37
  self.history: list[tuple[str, str]] = []
39
- super().__init__(message)
38
+ super().__init__(bot, message, *members)
40
39
 
41
40
  @override
42
41
  async def on_start(self) -> None:
@@ -64,14 +63,15 @@ class RockPaperScissor(Game):
64
63
  label: str,
65
64
  ) -> None:
66
65
  update = False
66
+ user = interaction.user
67
67
 
68
68
  # Player 1 click on button
69
- if interaction.user == self.player1 and self.play1 is None:
69
+ if user == self.players[0].member and self.play1 is None:
70
70
  self.play1 = label
71
71
  update = True
72
72
 
73
73
  # Player 2 click on button
74
- elif interaction.user == self.player2 and self.play2 is None:
74
+ elif user == self.players[1].member and self.play2 is None:
75
75
  self.play2 = label
76
76
  update = True
77
77
 
@@ -105,15 +105,15 @@ class RockPaperScissor(Game):
105
105
  if self.play1 is None and self.play2 is None:
106
106
  icon_url = self.bot.app_emojis["wait"].url
107
107
  info = (
108
- f"En attente de {self.player1.mention} "
109
- f"et {self.player2.mention} ..."
108
+ f"En attente de {self.players[0].member.mention} "
109
+ f"et {self.players[1].member.mention} ..."
110
110
  )
111
111
  elif self.play1 is None:
112
- icon_url = self.player1.display_avatar.url
113
- info = f"En attente de {self.player1.mention} ..."
112
+ icon_url = self.players[0].member.display_avatar.url
113
+ info = f"En attente de {self.players[0].member.mention} ..."
114
114
  elif self.play2 is None:
115
- icon_url = self.player2.display_avatar.url
116
- info = f"En attente de {self.player2.mention} ..."
115
+ icon_url = self.players[1].member.display_avatar.url
116
+ info = f"En attente de {self.players[1].member.mention} ..."
117
117
  else:
118
118
  # Play and fight
119
119
  self.history.append((self.play1, self.play2))
@@ -121,8 +121,8 @@ class RockPaperScissor(Game):
121
121
  self.play2 = None
122
122
  icon_url = self.bot.app_emojis["wait"].url
123
123
  info = (
124
- f"En attente de {self.player1.mention} "
125
- f"et {self.player2.mention} ..."
124
+ f"En attente de {self.players[0].member.mention} "
125
+ f"et {self.players[1].member.mention} ..."
126
126
  )
127
127
  pt1 = 0
128
128
  pt2 = 0
@@ -132,10 +132,10 @@ class RockPaperScissor(Game):
132
132
  i2 = EMOJIS.index(play2)
133
133
  if i1 == (i2 - 1) % 3:
134
134
  pt1 += 1
135
- text = self.player1.mention
135
+ text = self.players[0].member.mention
136
136
  elif i1 == (i2 + 1) % 3:
137
137
  pt2 += 1
138
- text = self.player2.mention
138
+ text = self.players[1].member.mention
139
139
  else:
140
140
  text = "**égalité**"
141
141
  morsels.append(
@@ -154,21 +154,21 @@ class RockPaperScissor(Game):
154
154
  if self.timeout:
155
155
  final_winner = self.winner
156
156
  elif pt1 < pt2:
157
- final_winner = self.player2
157
+ final_winner = self.players[1]
158
158
  elif pt2 < pt1:
159
- final_winner = self.player1
159
+ final_winner = self.players[0]
160
160
  else:
161
161
  final_winner = None
162
162
  if final_winner:
163
163
  forfait = "par forfait " if self.timeout else ""
164
164
  embed.description += (
165
- f"## Gagnant {forfait}{final_winner.mention} 🎉"
165
+ f"## Gagnant {forfait}{final_winner.member.mention} 🎉"
166
166
  )
167
- icon_url = final_winner.display_avatar.url
167
+ icon_url = final_winner.member.display_avatar.url
168
168
  else:
169
169
  embed.description += (
170
- f"## Égalité entre {self.player1.mention} "
171
- f"et {self.player2.mention} 🤝"
170
+ f"## Égalité entre {self.players[0].member.mention} "
171
+ f"et {self.players[1].member.mention} 🤝"
172
172
  )
173
173
  icon_url = self.bot.app_emojis["end"].url
174
174
  self.view.stop()
@@ -182,7 +182,10 @@ class RockPaperScissor(Game):
182
182
  await self.message.edit(
183
183
  embed=embed,
184
184
  view=self.view,
185
- content=(f"-# {self.player1.mention} {self.player2.mention}"),
185
+ content=(
186
+ f"-# {self.players[0].member.mention} "
187
+ f"{self.players[1].member.mention}"
188
+ ),
186
189
  )
187
190
 
188
191
  def compute_winner(
@@ -192,9 +195,9 @@ class RockPaperScissor(Game):
192
195
  i1 = EMOJIS.index(play1)
193
196
  i2 = EMOJIS.index(play2)
194
197
  if i1 == (i2 + 1) % 3:
195
- return self.player1
198
+ return self.players[0].member
196
199
  if i1 == (i2 - 1) % 3:
197
- return self.player2
200
+ return self.players[1].member
198
201
  return None # Draw
199
202
 
200
203
  def color(
@@ -203,9 +206,9 @@ class RockPaperScissor(Game):
203
206
  """Color of the embed."""
204
207
  if member is None:
205
208
  return discord.Colour.from_str("#d4d5d6")
206
- if member == self.player1:
209
+ if member == self.players[0]:
207
210
  return discord.Colour.from_str("#ca2a3e")
208
- if member == self.player2:
211
+ if member == self.players[1]:
209
212
  return discord.Colour.from_str("#5865F2")
210
213
  error_message = f"Invalid member: {member!r}"
211
214
  raise ValueError(error_message)
@@ -214,7 +217,7 @@ class RockPaperScissor(Game):
214
217
  async def on_timeout(self) -> None:
215
218
  self.timeout = True
216
219
  if self.play1 is None:
217
- await self.set_winner(self.player2)
220
+ await self.set_winner(self.players[1])
218
221
  if self.play2 is None:
219
- await self.set_winner(self.player1)
222
+ await self.set_winner(self.players[0])
220
223
  await self.update()