Guides
Button game
Build rock, paper, scissors with buttons. Only the player can click, and the game ends on its own if they walk away.
You will make /rps. The bot shows three buttons. The player picks one, the bot picks one, and the reply shows who won.
You picked rock, I picked scissors. You win!
This guide uses a View. A View is a set of buttons that lives inside one run of a command. The buttons, the code that runs on a click, and the result all stay in the same command. You do not need an event for it.
1. Create the command#
Open Create → Slash Command and switch to Advanced at the top of the form.
| Field | Value |
|---|---|
| Command Name | rps |
| Description | Play rock, paper, scissors |
Leave the Content / Reply Message box empty. The code writes the reply.
2. Add the buttons#
Open Pre-hook and paste:
class Game(View):
def __init__(self, user_id):
self.user_id = user_id # only this member can click
self.pick = None
async def choose(self, interaction, pick):
await defer(interaction, action="update")
self.pick = pick
self.stop() # ends the game; the post-hook runs next
@button(label="Rock", emoji="🪨", style="secondary", custom_id="rock")
async def rock(self, interaction, component):
await self.choose(interaction, "rock")
@button(label="Paper", emoji="📄", style="secondary", custom_id="paper")
async def paper(self, interaction, component):
await self.choose(interaction, "paper")
@button(label="Scissors", emoji="✂️", style="secondary", custom_id="scissors")
async def scissors(self, interaction, component):
await self.choose(interaction, "scissors")
game = Game(user_id=user.id)
game.interaction = interaction
set_view(game)
set_content(f"{user.mention}, pick one!")What each part does:
| Part | Job |
|---|---|
class Game(View) | Makes a new set of buttons. |
self.user_id = user_id | Only the member who ran /rps can click. Other clicks are ignored. |
@button(...) | Adds one button. The function under it runs on a click. |
defer(interaction, action="update") | Tells Discord "click received" within its 3-second limit. |
self.stop() | Ends the View. No code after it in that function runs. |
set_view(game) | Puts the buttons on the reply and waits for clicks. |
Watch out
Do not call super().__init__() in your View, and do not call game.wait() yourself. set_view does the waiting for you.
3. Show the result#
Open Post-hook and paste:
if game.pick is None:
result = "Too slow! The game ended."
else:
bot_pick = random(["rock", "paper", "scissors"])
beats = {"rock": "scissors", "paper": "rock", "scissors": "paper"}
if game.pick == bot_pick:
outcome = "It's a tie!"
elif beats[game.pick] == bot_pick:
outcome = "You win!"
else:
outcome = "I win!"
result = f"You picked **{game.pick}**, I picked **{bot_pick}**. {outcome}"
await respond_interaction(interaction, content=result)The post-hook runs after the View ends. That happens after a click, or after 3 minutes with no click. If the time runs out, game.pick is still None.
respond_interaction changes the reply text and removes the buttons.
4. Try it#
Save, then run /rps in your server. Click a button.
Ask a friend to click your buttons. Nothing happens for them, because of user_id.
Make it better#
Keep score#
Save wins with a member variable. Add this to the post-hook, just before the respond_interaction line:
if game.pick is not None and outcome == "You win!":
wins = int(member_var("rps_wins", 0)) + 1
update_member_var("rps_wins", wins)
result = f"{result} You have {wins} wins."Member variables are saved as text, so int(...) turns the value back into a number. See Saving data.
Learn more#
- Buttons and menus explains Views and when to use an event instead.
- Views lists every View tool, like
update_componentanddisable_all_components. - Interactions covers
deferandrespond_interaction.