-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroshidere.py
More file actions
542 lines (447 loc) · 23.1 KB
/
roshidere.py
File metadata and controls
542 lines (447 loc) · 23.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
import discord
from discord import app_commands
from discord.ext import commands
import random
import asyncio
from datetime import timedelta
from util_discord import command_check, description_helper
# Constants
MAX_ROUNDS = 6 # Total number of chambers/rounds
MIN_BULLETS = 1 # Minimum number of "live" rounds
MAX_BULLETS = 3 # Maximum number of "live" rounds
GAME_DURATION = 300 # Default game duration in seconds (increased for two-player games)
PLAYERS_REQUIRED = 2 # Exactly two players required
async def RUSSIAN_ROULETTE(ctx: commands.Context | discord.Interaction, duration: int = GAME_DURATION):
"""Main function to start and manage a Russian Roulette game"""
if await command_check(ctx, "roulette", "games"):
if isinstance(ctx, commands.Context):
return await ctx.reply("command disabled", ephemeral=True)
if isinstance(ctx, discord.Interaction):
return await ctx.response.send_message("command disabled", ephemeral=True)
# Initialize game state
bullet_count = random.randint(MIN_BULLETS, MAX_BULLETS)
chambers = [False] * MAX_ROUNDS # False = safe, True = bullet
# Randomly place bullets in chambers
bullet_positions = random.sample(range(MAX_ROUNDS), bullet_count)
for position in bullet_positions:
chambers[position] = True
game_state = {
"players": {}, # Player data: {user_id: {name, alive, turns_played}}
"player_ids": [], # To maintain turn order
"chambers": chambers,
"current_chamber": 0,
"waiting_for_players": True, # New state for waiting phase
"running": True,
"start_time": None, # Will be set when game actually starts
"end_time": None, # Will be set when game actually starts
"duration": duration,
"current_player_index": 0, # Track whose turn it is
"eliminated_players": [],
"winners": [],
"bullet_count": bullet_count,
"game_log": ["Game created! Waiting for 2 players to join..."]
}
# Send initial game message
embed = build_waiting_embed(game_state)
view = RussianRouletteView(game_state)
if isinstance(ctx, commands.Context):
message = await ctx.reply(embed=embed, view=view)
else:
await ctx.response.send_message(embed=embed, view=view)
message = await ctx.original_response()
# Start game update loop
try:
while game_state["running"]:
# If still waiting for players
if game_state["waiting_for_players"]:
if len(game_state["players"]) >= PLAYERS_REQUIRED:
# Transition from waiting to playing
game_state["waiting_for_players"] = False
game_state["start_time"] = discord.utils.utcnow()
game_state["end_time"] = discord.utils.utcnow() + timedelta(seconds=duration)
game_state["game_log"].append("🎮 Both players have joined! Game is starting...")
# Fix the player order based on join time
game_state["player_ids"] = list(game_state["players"].keys())
# Update the join button to a pull trigger button
view = RussianRouletteView(game_state)
embed = build_game_embed(game_state)
await message.edit(embed=embed, view=view)
else:
# Regular game running checks
# Check if game should end by timeout
if discord.utils.utcnow() >= game_state["end_time"]:
game_state["running"] = False
game_state["game_log"].append("⏰ Time's up! Game over.")
# Any surviving players are winners
alive_players = [player_id for player_id, data in game_state["players"].items() if data["alive"]]
if alive_players:
game_state["winners"] = [game_state["players"][player_id]["name"] for player_id in alive_players]
game_state["game_log"].append(f"Survivor: {', '.join(game_state['winners'])}")
else:
game_state["game_log"].append("Everyone was eliminated!")
# Check if all bullets have been fired
fired_chambers = game_state["current_chamber"]
bullets_left = sum(game_state["chambers"][fired_chambers:])
if fired_chambers >= MAX_ROUNDS or (bullets_left == 0 and fired_chambers > 0):
game_state["running"] = False
game_state["game_log"].append("🎮 All rounds complete!")
# Add survivors to winners
alive_players = [player_id for player_id, data in game_state["players"].items() if data["alive"]]
if alive_players:
game_state["winners"] = [game_state["players"][player_id]["name"] for player_id in alive_players]
game_state["game_log"].append(f"Survivor: {', '.join(game_state['winners'])}")
else:
game_state["game_log"].append("Everyone was eliminated!")
# Check if only one player is left alive
alive_players = [player_id for player_id, data in game_state["players"].items() if data["alive"]]
if len(alive_players) == 1 and len(game_state["players"]) >= PLAYERS_REQUIRED:
winner_name = game_state["players"][alive_players[0]]["name"]
game_state["winners"] = [winner_name]
game_state["running"] = False
game_state["game_log"].append(f"🏆 {winner_name} is the last player standing!")
# Update the game display
embed = build_game_embed(game_state)
await message.edit(embed=embed, view=view if game_state["running"] else None)
await asyncio.sleep(1)
# Show final results
embed = build_results_embed(game_state)
await message.edit(embed=embed, view=None)
except Exception as e:
print(f"Error in Russian Roulette game: {e}")
game_state["running"] = False
embed = discord.Embed(
title="Russian Roulette - Error",
description=f"Game stopped due to an error: {e}",
color=0xff0000
)
await message.edit(embed=embed, view=None)
def build_waiting_embed(game_state):
"""Create the waiting for players embed"""
player_count = len(game_state["players"])
players_needed = PLAYERS_REQUIRED - player_count
# Create embed
embed = discord.Embed(
title="🔫 Russian Roulette - Waiting for Players 🎲",
description=f"**Waiting for {players_needed} more player{'s' if players_needed != 1 else ''} to join!**\nPress the Join button to enter the game.",
color=0xff5500
)
# Add player list
if player_count > 0:
player_names = [data["name"] for data in game_state["players"].values()]
embed.add_field(
name=f"Players ({player_count}/{PLAYERS_REQUIRED})",
value="\n".join(player_names),
inline=False
)
return embed
def build_game_embed(game_state):
"""Create the game display embed"""
# Time remaining calculation
if game_state["running"] and not game_state["waiting_for_players"]:
time_remaining = (game_state["end_time"] - discord.utils.utcnow()).total_seconds()
time_str = f"{int(time_remaining // 60):02d}:{int(time_remaining % 60):02d}"
else:
time_str = "Game Over!" if not game_state["waiting_for_players"] else "Waiting for players..."
# Player count
player_count = len(game_state["players"])
alive_count = sum(1 for player in game_state["players"].values() if player["alive"])
eliminated_count = len(game_state["eliminated_players"])
# Create the revolver visual
revolver = build_revolver_visual(game_state)
# Determine whose turn it is
current_turn_text = ""
if game_state["running"] and not game_state["waiting_for_players"] and len(game_state["player_ids"]) > 0:
current_player_index = game_state["current_player_index"] % len(game_state["player_ids"])
current_player_id = game_state["player_ids"][current_player_index]
if current_player_id in game_state["players"] and game_state["players"][current_player_id]["alive"]:
current_player_name = game_state["players"][current_player_id]["name"]
current_turn_text = f"\n**🎯 Current Turn: {current_player_name}**"
# Create embed
embed = discord.Embed(
title="🔫 Russian Roulette 🎲",
description=f"**Take turns pulling the trigger!**\n{revolver}\nTime: {time_str}{current_turn_text}",
color=0xff5500
)
# Add player stats
embed.add_field(
name=f"Players ({alive_count} alive, {eliminated_count} eliminated)",
value=format_players(game_state) or "No players yet"
)
# Add game log field
log_entries = game_state["game_log"][-5:] # Show last 5 entries
log_text = "\n".join(log_entries)
embed.add_field(
name="Game Log",
value=log_text,
inline=False
)
# Add chamber info (debug info that can be removed in production)
if not game_state["running"] and not game_state["waiting_for_players"]:
chambers_info = " ".join(["🔴" if chamber else "⚪" for chamber in game_state["chambers"]])
embed.add_field(
name="Chambers Revealed",
value=chambers_info,
inline=False
)
return embed
def format_players(game_state):
"""Format player names with their status"""
player_strings = []
# First add alive players
for player_id, player_data in game_state["players"].items():
if player_data["alive"]:
status = "🟢" if player_data["alive"] else "💀"
turns = player_data.get("turns_played", 0)
player_strings.append(f"{status} {player_data['name']} ({turns} turns)")
# Then add eliminated players
for player_name in game_state["eliminated_players"]:
player_strings.append(f"💀 ~~{player_name}~~")
return "\n".join(player_strings)
def build_results_embed(game_state):
"""Create the final results embed"""
# Player stats
player_count = len(game_state["players"])
alive_count = sum(1 for player in game_state["players"].values() if player["alive"])
eliminated_count = len(game_state["eliminated_players"])
# Determine color based on if there are winners
color = 0x00ff00 if game_state["winners"] else 0xff0000
# Create embed
embed = discord.Embed(
title="🔫 Russian Roulette - Game Over! 🎲",
color=color
)
# Add winners or "everyone eliminated" message
if game_state["winners"]:
winners_text = ", ".join(game_state["winners"])
embed.description = f"**WINNER:** {winners_text} 👑\nThey faced death and lived to tell the tale!"
else:
embed.description = "**GAME OVER**\nNo one survived! The Grim Reaper claims all!"
# Add revolver visualization
revolver = build_revolver_visual(game_state, reveal_all=True)
embed.add_field(
name="Final Chamber State",
value=revolver,
inline=False
)
# Add player stats
embed.add_field(
name=f"Players ({alive_count} alive, {eliminated_count} eliminated)",
value=format_players(game_state) or "No players participated",
inline=False
)
# Add game log
log_entries = game_state["game_log"][-8:] # Show more entries in final results
log_text = "\n".join(log_entries)
embed.add_field(
name="Game Log",
value=log_text,
inline=False
)
return embed
def build_revolver_visual(game_state, reveal_all=False):
"""Create a visual representation of the revolver's chambers"""
chambers = game_state["chambers"]
current_pos = game_state["current_chamber"]
# Create visuals for each chamber
chamber_visuals = []
for i in range(MAX_ROUNDS):
if i < current_pos:
# Already fired chambers
if chambers[i]:
chamber_visuals.append("💥") # Fired, had bullet
else:
chamber_visuals.append("🔳") # Fired, was empty
elif i == current_pos and game_state["running"] and not game_state["waiting_for_players"]:
chamber_visuals.append("🔄") # Current chamber
else:
# Future chambers or game over reveal
if reveal_all:
if chambers[i]:
chamber_visuals.append("🔴") # Unfired bullet
else:
chamber_visuals.append("⚪") # Unfired empty
else:
chamber_visuals.append("⬜") # Unknown
# Create the revolver visual
revolver = " ".join(chamber_visuals)
# Add additional info
bullets_info = f"Revolver loaded with {game_state['bullet_count']} bullets"
if reveal_all or (not game_state["waiting_for_players"] and current_pos > 0):
bullets_info += f" | Fired: {current_pos}/{MAX_ROUNDS} chambers"
return f"{revolver}\n{bullets_info}"
class RussianRouletteView(discord.ui.View):
def __init__(self, game_state):
super().__init__(timeout=None)
self.game_state = game_state
# Different buttons based on game state
if game_state["waiting_for_players"]:
self.add_item(JoinGameButton("Join Game", "join", "👤", discord.ButtonStyle.primary, 0))
else:
self.add_item(RoulettePullButton("Shoot Self", "self", "🎯", discord.ButtonStyle.danger, 0))
self.add_item(RouletteOpponentButton("Shoot Opponent", "opponent", "👥", discord.ButtonStyle.secondary, 0))
class JoinGameButton(discord.ui.Button):
def __init__(self, label, action_id, emoji, style, row):
super().__init__(style=style, label=label, emoji=emoji, row=row)
self.action_id = action_id
async def callback(self, interaction: discord.Interaction):
game_state = self.view.game_state
# Check if game is still in waiting state
if not game_state["waiting_for_players"]:
return await interaction.response.send_message("The game has already started!", ephemeral=True)
user_id = interaction.user.id
user_name = interaction.user.display_name
# Check if player already joined
if user_id in game_state["players"]:
return await interaction.response.send_message("You've already joined this game!", ephemeral=True)
# Check if game is full
if len(game_state["players"]) >= PLAYERS_REQUIRED:
return await interaction.response.send_message("This game is already full!", ephemeral=True)
# Add player to the game
game_state["players"][user_id] = {
"name": user_name,
"alive": True,
"turns_played": 0
}
# Add to player order list
game_state["player_ids"].append(user_id)
# Update game log
game_state["game_log"].append(f"👤 **{user_name}** has joined the game!")
# Update the embed
embed = build_waiting_embed(game_state)
await interaction.response.edit_message(embed=embed)
class RouletteOpponentButton(discord.ui.Button):
def __init__(self, label, action_id, emoji, style, row):
super().__init__(style=style, label=label, emoji=emoji, row=row)
self.action_id = action_id
async def callback(self, interaction: discord.Interaction):
game_state = self.view.game_state
# Check if game is still running
if not game_state["running"] or game_state["waiting_for_players"]:
return await interaction.response.send_message(
"The game isn't ready for play yet!" if game_state["waiting_for_players"] else "The game has ended!",
ephemeral=True
)
user_id = interaction.user.id
user_name = interaction.user.display_name
# Check if it's this player's turn
current_player_index = game_state["current_player_index"] % len(game_state["player_ids"])
current_player_id = game_state["player_ids"][current_player_index]
if user_id != current_player_id:
current_player_name = game_state["players"][current_player_id]["name"]
return await interaction.response.send_message(
f"It's not your turn! Waiting for {current_player_name} to play.",
ephemeral=True
)
# Check if player is already eliminated
if not game_state["players"][user_id]["alive"]:
return await interaction.response.send_message(
"You've been eliminated! You can't play anymore.",
ephemeral=True
)
# Find opponent (in a two-player game, it's the other player)
opponent_id = None
for player_id in game_state["player_ids"]:
if player_id != user_id and game_state["players"][player_id]["alive"]:
opponent_id = player_id
break
if opponent_id is None:
return await interaction.response.send_message(
"No opponent found to shoot at!",
ephemeral=True
)
opponent_name = game_state["players"][opponent_id]["name"]
# Process their turn
current_chamber = game_state["current_chamber"]
if current_chamber < MAX_ROUNDS:
# Check if current chamber has a bullet
if game_state["chambers"][current_chamber]:
# Opponent is eliminated
game_state["players"][opponent_id]["alive"] = False
game_state["eliminated_players"].append(opponent_name)
game_state["game_log"].append(f"💥 **{user_name}** shot **{opponent_name}** and eliminated them!")
else:
# Shot missed
game_state["game_log"].append(f"🔄 **{user_name}** shot at **{opponent_name}** but the chamber was empty!")
# Increment player's turn count
game_state["players"][user_id]["turns_played"] += 1
# Move to next chamber
game_state["current_chamber"] += 1
# Move to next player's turn
game_state["current_player_index"] = (game_state["current_player_index"] + 1) % len(game_state["player_ids"])
# Skip eliminated players for next turn
while (
len(game_state["player_ids"]) > 0 and
not game_state["players"][game_state["player_ids"][game_state["current_player_index"]]]["alive"]
):
game_state["current_player_index"] = (game_state["current_player_index"] + 1) % len(game_state["player_ids"])
await interaction.response.defer()
class RoulettePullButton(discord.ui.Button):
def __init__(self, label, action_id, emoji, style, row):
super().__init__(style=style, label=label, emoji=emoji, row=row)
self.action_id = action_id
async def callback(self, interaction: discord.Interaction):
game_state = self.view.game_state
# Check if game is still running
if not game_state["running"] or game_state["waiting_for_players"]:
return await interaction.response.send_message(
"The game isn't ready for play yet!" if game_state["waiting_for_players"] else "The game has ended!",
ephemeral=True
)
user_id = interaction.user.id
user_name = interaction.user.display_name
# Check if it's this player's turn
current_player_index = game_state["current_player_index"] % len(game_state["player_ids"])
current_player_id = game_state["player_ids"][current_player_index]
if user_id != current_player_id:
current_player_name = game_state["players"][current_player_id]["name"]
return await interaction.response.send_message(
f"It's not your turn! Waiting for {current_player_name} to play.",
ephemeral=True
)
# Check if player is already eliminated
if not game_state["players"][user_id]["alive"]:
return await interaction.response.send_message(
"You've been eliminated! You can't play anymore.",
ephemeral=True
)
# Process their turn
current_chamber = game_state["current_chamber"]
gets_extra_turn = False
if current_chamber < MAX_ROUNDS:
# Check if current chamber has a bullet
if game_state["chambers"][current_chamber]:
# Player is eliminated
game_state["players"][user_id]["alive"] = False
game_state["eliminated_players"].append(game_state["players"][user_id]["name"])
game_state["game_log"].append(f"💥 **{user_name}** shot themself and was eliminated!")
else:
# Player survived this round and gets an extra turn
game_state["game_log"].append(f"🔄 **{user_name}** shot themself, survived, and gets another turn!")
gets_extra_turn = True
# Increment player's turn count
game_state["players"][user_id]["turns_played"] += 1
# Move to next chamber
game_state["current_chamber"] += 1
# Only advance turn if player doesn't get an extra turn
if not gets_extra_turn:
# Move to next player's turn
game_state["current_player_index"] = (game_state["current_player_index"] + 1) % len(game_state["player_ids"])
# Skip eliminated players for next turn
while (
len(game_state["player_ids"]) > 0 and
not game_state["players"][game_state["player_ids"][game_state["current_player_index"]]]["alive"]
):
game_state["current_player_index"] = (game_state["current_player_index"] + 1) % len(game_state["player_ids"])
await interaction.response.defer()
class CogRussianRoulette(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.hybrid_command(description=f"{description_helper['emojis']['games']} {description_helper['games']['roulette']}")
@app_commands.describe(duration="Duration (seconds)")
@app_commands.allowed_installs(guilds=True, users=True)
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
async def roulette(self, ctx: commands.Context, duration: int = GAME_DURATION):
"""Start a Russian Roulette game"""
await RUSSIAN_ROULETTE(ctx, duration)
async def setup(bot: commands.Bot):
await bot.add_cog(CogRussianRoulette(bot))