Experimental Discord bot written in Python
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

gamescog.py 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import math
  2. import random
  3. from time import perf_counter
  4. from discord import Interaction, UnfurledMediaItem
  5. from discord.app_commands import command, Range, guild_only
  6. from discord.ui import LayoutView, Section, TextDisplay, Thumbnail, Container
  7. from rocketbot.bot import Rocketbot
  8. from rocketbot.cogs.basecog import BaseCog
  9. class GamesCog(BaseCog, name="Games"):
  10. """Some really basic games or approximations thereof."""
  11. def __init__(self, bot: Rocketbot):
  12. super().__init__(
  13. bot,
  14. config_prefix='games',
  15. short_description='Some stupid, low effort games.',
  16. )
  17. @command(
  18. description='Rolls a die and gives the result. Provides seconds of amusement.'
  19. )
  20. @guild_only
  21. async def roll(self, interaction: Interaction, sides: Range[int, 2, 100], share: bool = False):
  22. """
  23. Rolls a die.
  24. Parameters
  25. ----------
  26. sides : Range[int, 2, 100]
  27. how many sides on the die
  28. share : bool
  29. whether to show the result to everyone in chat, otherwise only shown to you
  30. """
  31. result = random.randint(1, sides)
  32. who = interaction.user.mention if share else 'You'
  33. text = f'## :game_die: D{sides} rolled\n'\
  34. '\n'\
  35. f' {who} rolled a **{result}**.'
  36. if result == 69:
  37. text += ' :smirk:'
  38. if sides == 20 and result == 1:
  39. text += ' :slight_frown:'
  40. if sides == 20 and result == 20:
  41. text += ' :tada:'
  42. # This is a lot of work for a dumb joke
  43. others = set()
  44. for _ in range(min(sides - 1, 3)):
  45. r = random.randint(1, sides)
  46. while r in others or r == result:
  47. r = random.randint(1, sides)
  48. others.add(r)
  49. text += f'\n\n-# Users who rolled {result} also liked {listed_items(list(others))}'
  50. await interaction.response.send_message(text, ephemeral=not share)
  51. self.log(interaction.guild, f'{interaction.user.name} used /roll {sides} {share}')
  52. @command(
  53. description='Creates a really terrible Minesweeper puzzle.'
  54. )
  55. @guild_only
  56. async def minesweeper(self, interaction: Interaction):
  57. MINE = -1
  58. BLANK = 0
  59. # Generate grid
  60. width, height = 8, 8 # about as big as you can get. 10x10 has too many spoiler blocks and Discord doesn't parse them.
  61. mine_count = 10
  62. grid = [[BLANK for _ in range(width)] for _ in range(height)]
  63. all_positions = [(x, y) for y in range(height) for x in range(width)]
  64. # Place mines randomly
  65. random.shuffle(all_positions)
  66. for x, y in all_positions[:mine_count]:
  67. grid[x][y] = MINE
  68. # Update free spaces with mine counts
  69. for y in range(height):
  70. for x in range(width):
  71. if grid[x][y] == MINE:
  72. continue
  73. nearby_mine_count = 0
  74. for y0 in range(y - 1, y + 2):
  75. for x0 in range(x - 1, x + 2):
  76. if 0 <= x0 < width and 0 <= y0 < height and grid[x0][y0] < 0:
  77. nearby_mine_count += 1
  78. grid[x][y] = nearby_mine_count
  79. # Pick a non-mine starting tile to reveal (favoring 0s)
  80. start_x, start_y = 0, 0
  81. for n in range(8):
  82. start_positions = [ pt for pt in all_positions if BLANK <= grid[pt[0]][pt[1]] <= n ]
  83. if len(start_positions) > 0:
  84. # sort by closeness to center
  85. start_positions.sort(key=lambda pt: math.fabs(pt[0] - width / 2) + math.fabs(pt[1] - height / 2))
  86. # pick a random one from the top 10
  87. start_x, start_y = random.choice(start_positions[:10])
  88. break
  89. # Render
  90. puzzle = ''
  91. symbols = [
  92. '\u274C', # cross mark (red X)
  93. '0\uFE0F\u20E3', # 0 + variation selector 16 + combining enclosing keycap
  94. '1\uFE0F\u20E3',
  95. '2\uFE0F\u20E3',
  96. '3\uFE0F\u20E3',
  97. '4\uFE0F\u20E3',
  98. '5\uFE0F\u20E3',
  99. '6\uFE0F\u20E3',
  100. '7\uFE0F\u20E3',
  101. '8\uFE0F\u20E3',
  102. ]
  103. for y in range(height):
  104. puzzle += ' '
  105. for x in range(width):
  106. is_revealed = x == start_x and y == start_y
  107. if not is_revealed:
  108. puzzle += '||'
  109. val = grid[x][y]
  110. puzzle += symbols[val + 1]
  111. if not is_revealed:
  112. puzzle += '||'
  113. puzzle += ' '
  114. puzzle += '\n'
  115. text = "## Minesweeper (kinda)\n"\
  116. f"Here's a really terrible randomized Minesweeper puzzle. Sorry. I did my best.¹\n"\
  117. "\n"\
  118. f"Uncover everything except the {mine_count} :x: mines. There's no game logic or anything. Just a markdown parlor trick. Police yourselves.\n"\
  119. "\n"\
  120. f"{puzzle}"\
  121. "\n"\
  122. "-# ¹ I didn't really do my best."
  123. await interaction.response.send_message(
  124. view=_MinesweeperLayout(text),
  125. ephemeral=True,
  126. )
  127. self.log(interaction.guild, f'{interaction.user.name} used /minesweeper')
  128. class _MinesweeperLayout(LayoutView):
  129. def __init__(self, text_content: str):
  130. super().__init__()
  131. text = TextDisplay(text_content)
  132. thumb = Thumbnail(UnfurledMediaItem('https://static.rksp.net/rocketbot/games/minesweeper/icon.png'), description='Minesweeper icon')
  133. section = Section(text, accessory=thumb)
  134. container = Container(section, accent_color=0xff0000)
  135. self.add_item(container)
  136. def listed_items(items: list) -> str:
  137. if len(items) == 0:
  138. return 'nothing'
  139. if len(items) == 1:
  140. return f'{items[0]}'
  141. if len(items) == 2:
  142. return f'{items[0]} and {items[1]}'
  143. return (', '.join([ str(i) for i in items[:-1]])) + f', and {items[-1]}'