Files
Syntrel/cogs/fun/minesweeper.py

507 lines
20 KiB
Python
Raw Permalink Normal View History

import random
from itertools import repeat
import discord
2025-10-26 18:38:19 -04:00
from discord import app_commands
from discord.ext import commands
2025-10-08 16:03:54 -04:00
import asyncio
import time
class ChallengeConfirmView(discord.ui.View):
def __init__(self, context, challenger, challenged, bomb_count, board):
super().__init__(timeout=60)
self.context = context
self.challenger = challenger
self.challenged = challenged
self.bomb_count = bomb_count
self.board = board
self.accepted = False
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.grey, emoji="<:Check:1417250703407186051>")
async def confirm_button(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user.id != self.challenged.id:
return await interaction.response.send_message("Only the challenged player can accept!", ephemeral=True)
self.accepted = True
self.stop()
embed = discord.Embed(
title="Challenge Accepted!",
description=f"🎮 **{self.challenger.mention}** vs **{self.challenged.mention}**\n\n💣 Total Bombs: `{self.bomb_count}`\n\n{self.challenger.mention} goes first! Click the buttons to reveal the grid. Avoid the bombs!",
color=0x00FF00
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
view = MsView(self.context, self.ExtractBlocks(), self.bomb_count, self.board, self.challenged)
message = await interaction.response.edit_message(embed=embed, view=view)
view.message = message
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.grey, emoji="<:Cross:1417250649514446849>")
async def cancel_button(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user.id != self.challenged.id:
return await interaction.response.send_message("Only the challenged player can decline!", ephemeral=True)
embed = discord.Embed(
title="Challenge Declined",
description=f"<a:woeisnuke:1432138229628276927> {self.challenged.mention} declined the minesweeper challenge from {self.challenger.mention}",
color=0xE02B2B
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
await interaction.response.edit_message(embed=embed, view=None)
self.stop()
async def on_timeout(self):
embed = discord.Embed(
title="Challenge Expired",
description=f"{self.challenged.mention} didn't respond to the minesweeper challenge from {self.challenger.mention}",
color=0xFFA500
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
try:
await self.message.edit(embed=embed, view=None)
except:
pass
def ExtractBlocks(self):
new_b = []
for x in self.board:
for y in x:
new_b.append(y)
return new_b
class RowButton(discord.ui.Button):
2025-10-26 18:38:19 -04:00
def __init__(self, ctx, label, custom_id, bombs, board, opponent=None):
super().__init__(label=label, style=discord.ButtonStyle.grey, custom_id=custom_id)
self.ctx = ctx
self.bombs = bombs
self.board = board
2025-10-26 18:38:19 -04:00
self.opponent = opponent
async def callback(self, interaction):
assert self.view is not None
view: MsView = self.view
2025-10-08 16:03:54 -04:00
current_time = time.time()
if current_time - view.last_interaction < 0.5:
try:
return await interaction.response.send_message("Please wait before clicking again.", ephemeral=True)
except:
return
view.last_interaction = current_time
try:
await interaction.response.defer()
except discord.errors.HTTPException as e:
if e.status == 429:
await asyncio.sleep(1)
return
raise
2025-10-26 18:38:19 -04:00
if view.opponent:
if interaction.user.id not in [self.ctx.author.id, view.opponent.id]:
return await interaction.followup.send(
"You cannot interact with these buttons.", ephemeral=True
)
if interaction.user.id != view.current_player.id:
return await interaction.followup.send(
f"It's {view.current_player.mention}'s turn!", ephemeral=True
)
else:
if interaction.user.id != self.ctx.author.id:
return await interaction.followup.send(
"You cannot interact with these buttons.", ephemeral=True
)
b_id = self.custom_id
if int(b_id[5:]) in view.moves:
return await interaction.followup.send("That part is already taken.", ephemeral=True)
if not view.bombs_generated:
view.generate_bombs(int(b_id[5:]))
self.bombs = view.bombs
if int(b_id[5:]) in self.bombs:
2025-10-26 18:38:19 -04:00
await view.RevealBombs(b_id, view.board, interaction)
else:
count = []
rawpos = int(b_id[5:])
pos = view.GetBoardPos(rawpos)
def checkpos(count, rawpos, pos):
pos = view.GetBoardPos(rawpos)
if not rawpos - 1 in self.bombs or pos == 0:
count.append(rawpos - 1)
if not rawpos + 1 in self.bombs or pos == 4:
count.append(rawpos + 1)
if not rawpos - 6 in self.bombs or pos == 0:
count.append(rawpos - 6)
if not rawpos - 4 in self.bombs or pos == 4:
count.append(rawpos - 4)
if not rawpos + 6 in self.bombs or pos == 4:
count.append(rawpos + 6)
if not rawpos + 4 in self.bombs or pos == 0:
count.append(rawpos + 4)
if not rawpos - 5 in self.bombs:
count.append(rawpos - 5)
if not rawpos + 5 in self.bombs:
count.append(rawpos + 5)
return count
count = checkpos(count, rawpos, pos)
number = 8-len(count)
self.label = str(number) if number > 0 else "0"
self.style = discord.ButtonStyle.green
pos = int(b_id[5:])
view.board[view.GetBoardRow(pos)][
view.GetBoardPos(pos)
] = str(number) if number > 0 else "0"
view.moves.append(pos)
2025-10-26 18:38:19 -04:00
if view.opponent:
if interaction.user.id == view.ctx.author.id:
view.player1_score += 1
else:
view.player2_score += 1
if number == 0:
await view.auto_reveal_safe_squares(rawpos, interaction)
2025-10-26 18:38:19 -04:00
if view.opponent:
view.current_player = view.opponent if view.current_player.id == view.ctx.author.id else view.ctx.author
if len(view.moves) + len(self.bombs) == 25:
await view.EndGame()
2025-10-08 16:03:54 -04:00
else:
try:
2025-10-26 18:38:19 -04:00
await view.update_embed(interaction)
2025-10-08 16:03:54 -04:00
except discord.errors.HTTPException as e:
if e.status == 429:
await asyncio.sleep(1)
else:
raise
class MsView(discord.ui.View):
2025-10-26 18:38:19 -04:00
def __init__(self, ctx, options, bomb_count, board, opponent=None):
2025-10-08 16:03:54 -04:00
super().__init__(timeout=300)
for i, op in enumerate(options):
2025-10-26 18:38:19 -04:00
self.add_item(RowButton(ctx, op, f"block{i}", [], board, opponent))
self.board = board
self.bombs = []
self.bomb_count = bomb_count
self.bombs_generated = False
self.moves = []
self.ctx = ctx
self.message = None
2025-10-08 16:03:54 -04:00
self.last_interaction = 0
2025-10-26 18:38:19 -04:00
self.opponent = opponent
self.current_player = ctx.author
self.player1_score = 0
self.player2_score = 0
2025-10-08 16:03:54 -04:00
def generate_bombs(self, first_move_pos):
bombpositions = []
excluded_positions = [0, 4, 20, 24, first_move_pos]
while len(bombpositions) < self.bomb_count:
random_index = random.randint(0, 24)
if random_index not in bombpositions and random_index not in excluded_positions:
bombpositions.append(random_index)
self.bombs = bombpositions
self.bombs_generated = True
for button in self.children:
if isinstance(button, RowButton):
button.bombs = self.bombs
2025-10-08 16:03:54 -04:00
async def on_timeout(self):
for button in self.children:
button.disabled = True
embed = discord.Embed(
2025-10-08 16:03:54 -04:00
title="Minesweeper",
description="Game timed out!",
color=0xFF0000
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
2025-10-08 16:03:54 -04:00
try:
await interaction.edit_original_response(embed=embed, view=self)
2025-10-08 16:03:54 -04:00
except:
pass
async def EndGame(self):
for button in self.children:
button.disabled = True
pos = int(button.custom_id[5:])
if pos in self.bombs:
button.label = "💣"
button.style = discord.ButtonStyle.red
self.board[self.GetBoardRow(pos)][self.GetBoardPos(pos)] = "💣"
2025-10-08 16:03:54 -04:00
2025-10-26 18:38:19 -04:00
if self.opponent:
if self.player1_score > self.player2_score:
winner = self.ctx.author
elif self.player2_score > self.player1_score:
winner = self.opponent
else:
winner = None
if winner:
description = f"🎉 **{winner.mention}** won!"
else:
description = f"🤝 It's a tie!"
else:
description = "Game Ended. You won!"
2025-10-08 16:03:54 -04:00
embed = discord.Embed(
title="Minesweeper",
2025-10-26 18:38:19 -04:00
description=description,
2025-10-08 16:03:54 -04:00
color=0x00FF00
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
try:
await self.message.edit(embed=embed, view=self)
except discord.errors.HTTPException as e:
if e.status == 429:
await asyncio.sleep(1)
else:
raise
self.stop()
2025-10-26 18:38:19 -04:00
async def update_embed(self, interaction):
if self.opponent:
embed = discord.Embed(
title="Minesweeper - Multiplayer",
description=f"💣 Total Bombs: `{self.bomb_count}`\n\n🎮 **Players:**\n{self.ctx.author.mention} vs {self.opponent.mention}\n\n**Current Turn:** {self.current_player.mention}",
color=0x7289DA
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
else:
embed = discord.Embed(
title="Minesweeper",
description=f"💣 Total Bombs: `{self.bomb_count}`\n\nClick the buttons to reveal the grid. Avoid the bombs!",
color=0x7289DA
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
await interaction.edit_original_response(embed=embed, view=self)
async def auto_reveal_safe_squares(self, center_pos, interaction):
positions_to_check = [center_pos]
revealed_positions = set()
2025-10-26 18:38:19 -04:00
current_player_id = interaction.user.id
while positions_to_check:
current_pos = positions_to_check.pop(0)
if current_pos in revealed_positions:
continue
revealed_positions.add(current_pos)
adjacent_positions = []
pos = self.GetBoardPos(current_pos)
if pos > 0 and current_pos - 1 not in self.bombs:
adjacent_positions.append(current_pos - 1)
if pos < 4 and current_pos + 1 not in self.bombs:
adjacent_positions.append(current_pos + 1)
if current_pos >= 5 and current_pos - 5 not in self.bombs:
adjacent_positions.append(current_pos - 5)
if current_pos <= 19 and current_pos + 5 not in self.bombs:
adjacent_positions.append(current_pos + 5)
if pos > 0 and current_pos >= 5 and current_pos - 6 not in self.bombs:
adjacent_positions.append(current_pos - 6)
if pos < 4 and current_pos >= 5 and current_pos - 4 not in self.bombs:
adjacent_positions.append(current_pos - 4)
if pos > 0 and current_pos <= 19 and current_pos + 4 not in self.bombs:
adjacent_positions.append(current_pos + 4)
if pos < 4 and current_pos <= 19 and current_pos + 6 not in self.bombs:
adjacent_positions.append(current_pos + 6)
for adj_pos in adjacent_positions:
if adj_pos not in self.moves and adj_pos not in revealed_positions:
adj_count = []
adj_pos_obj = self.GetBoardPos(adj_pos)
def checkpos_adj(count, rawpos, pos):
pos = self.GetBoardPos(rawpos)
if not rawpos - 1 in self.bombs or pos == 0:
count.append(rawpos - 1)
if not rawpos + 1 in self.bombs or pos == 4:
count.append(rawpos + 1)
if not rawpos - 6 in self.bombs or pos == 0:
count.append(rawpos - 6)
if not rawpos - 4 in self.bombs or pos == 4:
count.append(rawpos - 4)
if not rawpos + 6 in self.bombs or pos == 4:
count.append(rawpos + 6)
if not rawpos + 4 in self.bombs or pos == 0:
count.append(rawpos + 4)
if not rawpos - 5 in self.bombs:
count.append(rawpos - 5)
if not rawpos + 5 in self.bombs:
count.append(rawpos + 5)
return count
adj_count = checkpos_adj(adj_count, adj_pos, adj_pos_obj)
adj_number = 8 - len(adj_count)
for button in self.children:
if int(button.custom_id[5:]) == adj_pos:
button.label = str(adj_number) if adj_number > 0 else "0"
button.style = discord.ButtonStyle.green
break
self.board[self.GetBoardRow(adj_pos)][self.GetBoardPos(adj_pos)] = str(adj_number) if adj_number > 0 else "0"
self.moves.append(adj_pos)
2025-10-26 18:38:19 -04:00
if self.opponent:
if current_player_id == self.ctx.author.id:
self.player1_score += 1
else:
self.player2_score += 1
if adj_number == 0:
positions_to_check.append(adj_pos)
try:
2025-10-26 18:38:19 -04:00
await self.update_embed(interaction)
except discord.errors.HTTPException as e:
if e.status == 429:
await asyncio.sleep(1)
else:
raise
def GetBoardRow(self, pos):
if pos in [0, 1, 2, 3, 4]:
return 0
if pos in [5, 6, 7, 8, 9]:
return 1
if pos in [10, 11, 12, 13, 14]:
return 2
if pos in [15, 16, 17, 18, 19]:
return 3
if pos in [20, 21, 22, 23, 24]:
return 4
return False
def GetBoardPos(self, pos):
if pos in [0, 1, 2, 3, 4]:
return pos
if pos in [5, 6, 7, 8, 9]:
for i, num in enumerate(range(5, 10)):
if pos == num:
return i
if pos in [10, 11, 12, 13, 14]:
for i, num in enumerate(range(10, 15)):
if pos == num:
return i
if pos in [15, 16, 17, 18, 19]:
for i, num in enumerate(range(15, 20)):
if pos == num:
return i
if pos in [20, 21, 22, 23, 24]:
for i, num in enumerate(range(20, 25)):
if pos == num:
return i
return False
2025-10-26 18:38:19 -04:00
async def RevealBombs(self, b_id, board, interaction):
bombemo = "💣"
for button in self.children:
button.disabled = True
if button.custom_id == b_id:
button.label = bombemo
button.style = discord.ButtonStyle.red
pos = int(b_id[5:])
self.board[self.GetBoardRow(pos)][self.GetBoardPos(pos)] = bombemo
for button in self.children:
if int(button.custom_id[5:]) in self.bombs:
button.label = bombemo
button.style = discord.ButtonStyle.red
pos = int(button.custom_id[5:])
self.board[self.GetBoardRow(pos)][
self.GetBoardPos(pos)
] = bombemo
2025-10-08 16:03:54 -04:00
2025-10-26 18:38:19 -04:00
if self.opponent:
loser = self.ctx.author if interaction.user.id == self.ctx.author.id else self.opponent
winner = self.opponent if loser.id == self.ctx.author.id else self.ctx.author
description = f"💥 BOOM! {loser.mention} hit a bomb!\n🎉 **{winner.mention}** wins!"
else:
description = f"💥 BOOM! You hit a bomb. Game Over!\n-# gg {self.ctx.author.mention}"
2025-10-08 16:03:54 -04:00
embed = discord.Embed(
title="Minesweeper",
2025-10-26 18:38:19 -04:00
description=description,
2025-10-08 16:03:54 -04:00
color=0xE02B2B
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
try:
await interaction.edit_original_response(embed=embed, view=self)
2025-10-08 16:03:54 -04:00
except discord.errors.HTTPException as e:
if e.status == 429:
await asyncio.sleep(1)
else:
raise
self.stop()
def minesweeper_command():
@commands.hybrid_command(
name="minesweeper",
description="Play a buttoned minesweeper mini-game."
)
2025-10-26 18:38:19 -04:00
@app_commands.describe(
opponent="Optional user to play against in multiplayer mode."
)
async def minesweeper(self, context, opponent: discord.User = None):
board = [[" "] * 5 for _ in range(5)]
bomb_count = random.randint(4, 11)
def ExtractBlocks():
new_b = []
for x in board:
for y in x:
new_b.append(y)
return new_b
2025-10-26 18:38:19 -04:00
if opponent:
if opponent.id == context.author.id:
embed = discord.Embed(
title="Error!",
description="You cannot play against yourself!",
color=0xE02B2B
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
return await context.send(embed=embed, ephemeral=True)
embed = discord.Embed(
title="Minesweeper Challenge",
description=f"🎮 **{opponent.mention}**, you are challenged by **{context.author.mention}** to a round of Minesweeper!\n\n💣 Total Bombs: `{bomb_count}`",
2025-10-26 18:38:19 -04:00
color=0x7289DA
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
view = ChallengeConfirmView(context, context.author, opponent, bomb_count, board)
message = await context.send(embed=embed, view=view)
view.message = message
2025-10-26 18:38:19 -04:00
else:
embed = discord.Embed(
title="Minesweeper",
description=f"💣 Total Bombs: `{bomb_count}`\n\nClick the buttons to reveal the grid. Avoid the bombs!",
color=0x7289DA
)
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
view = MsView(context, ExtractBlocks(), bomb_count, board, opponent)
message = await context.send(embed=embed, view=view)
view.message = message
return minesweeper