2025-09-15 17:20:59 -04:00
|
|
|
|
import random
|
|
|
|
|
|
from itertools import repeat
|
|
|
|
|
|
import discord
|
|
|
|
|
|
from discord.ext import commands
|
2025-10-08 16:03:54 -04:00
|
|
|
|
import asyncio
|
|
|
|
|
|
import time
|
2025-09-15 17:20:59 -04:00
|
|
|
|
|
|
|
|
|
|
class RowButton(discord.ui.Button):
|
|
|
|
|
|
def __init__(self, ctx, label, custom_id, bombs, board):
|
|
|
|
|
|
super().__init__(label=label, style=discord.ButtonStyle.grey, custom_id=custom_id)
|
|
|
|
|
|
self.ctx = ctx
|
|
|
|
|
|
self.bombs = bombs
|
|
|
|
|
|
self.board = board
|
|
|
|
|
|
|
|
|
|
|
|
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-09-15 17:20:59 -04:00
|
|
|
|
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)
|
2025-10-11 11:55:53 -04:00
|
|
|
|
|
|
|
|
|
|
if not view.bombs_generated:
|
|
|
|
|
|
view.generate_bombs(int(b_id[5:]))
|
|
|
|
|
|
self.bombs = view.bombs
|
|
|
|
|
|
|
2025-09-15 17:20:59 -04:00
|
|
|
|
if int(b_id[5:]) in self.bombs:
|
|
|
|
|
|
await view.RevealBombs(b_id, view.board)
|
|
|
|
|
|
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:21:08 -04:00
|
|
|
|
|
|
|
|
|
|
if number == 0:
|
|
|
|
|
|
await view.auto_reveal_safe_squares(rawpos, interaction)
|
|
|
|
|
|
|
2025-09-15 17:20:59 -04:00
|
|
|
|
if len(view.moves) + len(self.bombs) == 25:
|
|
|
|
|
|
await view.EndGame()
|
2025-10-08 16:03:54 -04:00
|
|
|
|
else:
|
|
|
|
|
|
try:
|
|
|
|
|
|
await interaction.edit_original_response(view=view)
|
|
|
|
|
|
except discord.errors.HTTPException as e:
|
|
|
|
|
|
if e.status == 429:
|
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise
|
2025-09-15 17:20:59 -04:00
|
|
|
|
|
|
|
|
|
|
class MsView(discord.ui.View):
|
2025-10-11 11:55:53 -04:00
|
|
|
|
def __init__(self, ctx, options, bomb_count, board):
|
2025-10-08 16:03:54 -04:00
|
|
|
|
super().__init__(timeout=300)
|
2025-09-15 17:20:59 -04:00
|
|
|
|
for i, op in enumerate(options):
|
2025-10-11 11:55:53 -04:00
|
|
|
|
self.add_item(RowButton(ctx, op, f"block{i}", [], board))
|
2025-09-15 17:20:59 -04:00
|
|
|
|
self.board = board
|
2025-10-11 11:55:53 -04:00
|
|
|
|
self.bombs = []
|
|
|
|
|
|
self.bomb_count = bomb_count
|
|
|
|
|
|
self.bombs_generated = False
|
2025-09-15 17:20:59 -04:00
|
|
|
|
self.moves = []
|
|
|
|
|
|
self.ctx = ctx
|
|
|
|
|
|
self.message = None
|
2025-10-08 16:03:54 -04:00
|
|
|
|
self.last_interaction = 0
|
|
|
|
|
|
|
2025-10-11 11:55:53 -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
|
2025-09-15 17:20:59 -04:00
|
|
|
|
embed = discord.Embed(
|
2025-10-08 16:03:54 -04:00
|
|
|
|
title="Minesweeper",
|
|
|
|
|
|
description="Game timed out!",
|
|
|
|
|
|
color=0xFF0000
|
2025-09-15 17:20:59 -04:00
|
|
|
|
)
|
|
|
|
|
|
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
2025-10-08 16:03:54 -04:00
|
|
|
|
try:
|
|
|
|
|
|
await self.message.edit(embed=embed, view=self)
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
async def EndGame(self):
|
2025-09-15 17:20:59 -04:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title="Minesweeper",
|
|
|
|
|
|
description="Game Ended. You won!",
|
|
|
|
|
|
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
|
2025-10-10 10:20:38 -04:00
|
|
|
|
self.stop()
|
2025-09-15 17:20:59 -04:00
|
|
|
|
|
2025-10-26 18:21:08 -04:00
|
|
|
|
async def auto_reveal_safe_squares(self, center_pos, interaction):
|
|
|
|
|
|
positions_to_check = [center_pos]
|
|
|
|
|
|
revealed_positions = set()
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
if adj_number == 0:
|
|
|
|
|
|
positions_to_check.append(adj_pos)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
await interaction.edit_original_response(view=self)
|
|
|
|
|
|
except discord.errors.HTTPException as e:
|
|
|
|
|
|
if e.status == 429:
|
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
2025-09-15 17:20:59 -04:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
async def RevealBombs(self, b_id, board):
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title="Minesweeper",
|
|
|
|
|
|
description=f"💥 BOOM! You hit a bomb. Game Over!\n-# gg {self.ctx.author.mention}",
|
|
|
|
|
|
color=0xE02B2B
|
|
|
|
|
|
)
|
|
|
|
|
|
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
|
2025-10-10 10:20:38 -04:00
|
|
|
|
self.stop()
|
2025-09-15 17:20:59 -04:00
|
|
|
|
|
2025-09-28 22:48:10 -04:00
|
|
|
|
def minesweeper_command():
|
2025-09-15 17:20:59 -04:00
|
|
|
|
@commands.hybrid_command(
|
|
|
|
|
|
name="minesweeper",
|
|
|
|
|
|
description="Play a buttoned minesweeper mini-game."
|
|
|
|
|
|
)
|
2025-09-28 22:48:10 -04:00
|
|
|
|
async def minesweeper(self, context):
|
|
|
|
|
|
board = [["឵឵ "] * 5 for _ in range(5)]
|
2025-10-11 11:55:53 -04:00
|
|
|
|
bomb_count = random.randint(4, 11)
|
2025-09-15 17:20:59 -04:00
|
|
|
|
|
|
|
|
|
|
def ExtractBlocks():
|
|
|
|
|
|
new_b = []
|
|
|
|
|
|
for x in board:
|
|
|
|
|
|
for y in x:
|
|
|
|
|
|
new_b.append(y)
|
|
|
|
|
|
return new_b
|
|
|
|
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title="Minesweeper",
|
2025-10-11 11:55:53 -04:00
|
|
|
|
description=f"💣 Total Bombs: `{bomb_count}`\n\nClick the buttons to reveal the grid. Avoid the bombs!",
|
2025-09-15 17:20:59 -04:00
|
|
|
|
color=0x7289DA
|
|
|
|
|
|
)
|
|
|
|
|
|
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
|
|
|
|
|
|
2025-10-11 11:55:53 -04:00
|
|
|
|
view = MsView(context, ExtractBlocks(), bomb_count, board)
|
2025-09-15 17:20:59 -04:00
|
|
|
|
message = await context.send(embed=embed, view=view)
|
|
|
|
|
|
view.message = message
|
2025-09-28 22:48:10 -04:00
|
|
|
|
|
|
|
|
|
|
return minesweeper
|