import math import random from discord import Interaction, UnfurledMediaItem from discord.app_commands import Range, command, guild_only from discord.ui import Container, LayoutView, Section, TextDisplay, Thumbnail from rocketbot.bot import Rocketbot from rocketbot.cogs.basecog import BaseCog class GamesCog(BaseCog, name="Games"): """Some really basic games or approximations thereof.""" def __init__(self, bot: Rocketbot): super().__init__( bot, config_prefix='games', short_description='Some stupid, low effort games.', ) @command( description='Rolls a die and gives the result. Provides seconds of amusement.' ) @guild_only async def roll(self, interaction: Interaction, sides: Range[int, 2, 100], share: bool = False): """ Rolls a die. Parameters ---------- sides : Range[int, 2, 100] how many sides on the die share : bool whether to show the result to everyone in chat, otherwise only shown to you """ result = random.randint(1, sides) who = interaction.user.mention if share else 'You' text = f'## :game_die: D{sides} rolled\n'\ '\n'\ f' {who} rolled a **{result}**.' if result == 69: text += ' :smirk:' if sides == 20 and result == 1: text += ' :slight_frown:' if sides == 20 and result == 20: text += ' :tada:' # This is a lot of work for a dumb joke others = set() for _ in range(min(sides - 1, 3)): r = random.randint(1, sides) while r in others or r == result: r = random.randint(1, sides) others.add(r) text += f'\n\n-# Users who rolled {result} also liked {listed_items(list(others))}' await interaction.response.send_message(text, ephemeral=not share) self.log(interaction.guild, f'{interaction.user.name} used /roll {sides} {share}') @command( description='Creates a really terrible Minesweeper puzzle.' ) @guild_only async def minesweeper(self, interaction: Interaction): MINE = -1 BLANK = 0 # Generate grid width, height = 8, 8 # about as big as you can get. 10x10 has too many spoiler blocks and Discord doesn't parse them. mine_count = 10 grid = [[BLANK for _ in range(width)] for _ in range(height)] all_positions = [(x, y) for y in range(height) for x in range(width)] # Place mines randomly random.shuffle(all_positions) for x, y in all_positions[:mine_count]: grid[x][y] = MINE # Update free spaces with mine counts for y in range(height): for x in range(width): if grid[x][y] == MINE: continue nearby_mine_count = 0 for y0 in range(y - 1, y + 2): for x0 in range(x - 1, x + 2): if 0 <= x0 < width and 0 <= y0 < height and grid[x0][y0] < 0: nearby_mine_count += 1 grid[x][y] = nearby_mine_count # Pick a non-mine starting tile to reveal (favoring 0s) start_x, start_y = 0, 0 for n in range(8): start_positions = [ pt for pt in all_positions if BLANK <= grid[pt[0]][pt[1]] <= n ] if len(start_positions) > 0: # sort by closeness to center start_positions.sort(key=lambda pt: math.fabs(pt[0] - width / 2) + math.fabs(pt[1] - height / 2)) # pick a random one from the top 10 start_x, start_y = random.choice(start_positions[:10]) break # Render puzzle = '' symbols = [ '\u274C', # cross mark (red X) '0\uFE0F\u20E3', # 0 + variation selector 16 + combining enclosing keycap '1\uFE0F\u20E3', '2\uFE0F\u20E3', '3\uFE0F\u20E3', '4\uFE0F\u20E3', '5\uFE0F\u20E3', '6\uFE0F\u20E3', '7\uFE0F\u20E3', '8\uFE0F\u20E3', ] for y in range(height): puzzle += ' ' for x in range(width): is_revealed = x == start_x and y == start_y if not is_revealed: puzzle += '||' val = grid[x][y] puzzle += symbols[val + 1] if not is_revealed: puzzle += '||' puzzle += ' ' puzzle += '\n' text = "## Minesweeper (kinda)\n"\ f"Here's a really terrible randomized Minesweeper puzzle. Sorry. I did my best.¹\n"\ "\n"\ f"Uncover everything except the {mine_count} :x: mines. There's no game logic or anything. Just a markdown parlor trick. Police yourselves.\n"\ "\n"\ f"{puzzle}"\ "\n"\ "-# ¹ I didn't really do my best." await interaction.response.send_message( view=_MinesweeperLayout(text), ephemeral=True, ) self.log(interaction.guild, f'{interaction.user.name} used /minesweeper') class _MinesweeperLayout(LayoutView): def __init__(self, text_content: str): super().__init__() text = TextDisplay(text_content) thumb = Thumbnail(UnfurledMediaItem('https://static.rksp.net/rocketbot/games/minesweeper/icon.png'), description='Minesweeper icon') section = Section(text, accessory=thumb) container = Container(section, accent_color=0xff0000) self.add_item(container) def listed_items(items: list) -> str: if len(items) == 0: return 'nothing' if len(items) == 1: return f'{items[0]}' if len(items) == 2: return f'{items[0]} and {items[1]}' return (', '.join([ str(i) for i in items[:-1]])) + f', and {items[-1]}'