| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- import math
- import random
- from time import perf_counter
-
- from discord import Interaction, UnfurledMediaItem
- from discord.app_commands import command, Range, guild_only
- from discord.ui import LayoutView, Section, TextDisplay, Thumbnail, Container
-
- 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)
-
- @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,
- )
-
- 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]}'
|