logoCustom Commands
    BlogPrivacyPremiumSupport
Back to blog
// blog

Create a tic-tac-toe game using custom commands bot

Lets create a discord slash command that allows playing multiplayer tic-tac-toe

Md Shahriyar Alam

2 years ago

·3 min read

First go to your specific server ccbot.appDashboard and click Create > Slash Command

Give it any name, in our example it will be tic-tac-toe and in the Command Description field put anything you want

Now move to the Hooks section and we need 2 views, first view is to allow someone to click a button to accept your challenge

py
p1 = user.id
p2 = None

class StartView(View):
    def __init__(self):
        self.user_id = None

    @button(label="Accept", style="success")
    async def accept(self, i, b):
        if i.user.id == p1:
            await defer(i, action="create", ephemeral=True)
            return respond_interaction(i, content="You can't play with youself")
        
        self.user_id = i.user.id
        await defer(i, action="update")
        respond_interaction(i, empty=True)
        self.stop()
        
view = StartView()
respond_interaction(
    interaction, 
    content=f"{user.mention} is challenging you to play Tic-Tac-Toe", component=view
)

exit_code = await view.wait(scope=scope, timeout=60)
if exit_code == "timeout":
  set_error("No one accepted the challenge.")

p2 = view.user_id

Since this game need 2 player, we initialized p1 to be the user who ran the command and for p2 we initially made it None and then made a view where anyone can click Accept button to be the second player. And when someone accepts we assign p2 to be the user id of that person

In the next part we make another view that controls the whole logic of the command.

py
async def check_winner(board):
    winning_combinations = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6] 
    ]
    
    is_game_over = False
    winner = None

    def check_combination(combination, index):
        nonlocal winner
        a, b, c = combination
        if board[a] is not None and board[a] == board[b] == board[c]:
            winner = board[a]
            break_loop() 
    
    loop(winning_combinations, check_combination)
    
    if (None not in board) or winner:
        is_game_over = True

    return [is_game_over, winner]

class GameView(View):
    def __init__(self):
        self.board = [
            None, None, None,
            None, None, None,
            None, None, None,
        ]

        self.players = [p1, p2]

        self.emojis = {
            p1: "⭕",
            p2: "❌"
        }
        
        self.current_player_index = random([0, 1]) # randomly select who goes first
        self.winner = None

    def get_current_player(self):
        return self.players[self.current_player_index]
    
    def change_player(self):
        self.current_player_index = 0 if self.current_player_index else 1
    
    async def validate_interaction(self, i):
        if i.user.id not in self.players:
            await defer(i, action="create", ephemeral=True)
            return respond_interaction(i, content="You are a member of this board") 

        if i.user.id != self.get_current_player():
            await defer(i, action="create", ephemeral=True)
            return respond_interaction(i, content="Its not your turn")

        return True
    
    async def update_board(self, i):
        await self.update_view()
        self.change_player()

        turn = get_member(self.get_current_player())
        respond_interaction(i, content=f"{turn.mention}'s turn")
    
    async def handle_turn(self, i, b):
        is_valid = await self.validate_interaction(i)
        if not is_valid:
            return

        await defer(i, action="update")
        self.board[b.metadata['index']] = i.user.id

        b.label = self.emojis[i.user.id]
        b.is_disabled = True

        is_game_over, winner = await check_winner(self.board)
        if winner:
            self.winner = winner
            self.stop()
            return

        if is_game_over:
            self.stop()
            return
        
        await self.update_board(i)

    @button(label="‎", row=1, index=0)
    async def one(self, i, b):
        await self.handle_turn(i, b)

    @button(label="‎", row=1, index=1)
    async def two(self, i, b):
        await self.handle_turn(i, b)

    @button(label="‎", row=1, index=2)
    async def three(self, i, b):
        await self.handle_turn(i, b)

    @button(label="‎", row=2, index=3)
    async def four(self, i, b):
        await self.handle_turn(i, b)

    @button(label="‎", row=2, index=4)
    async def five(self, i, b):
        await self.handle_turn(i, b)

    @button(label="‎", row=2, index=5)
    async def six(self, i, b):
        await self.handle_turn(i, b)

    @button(label="‎", row=3, index=6)
    async def seven(self, i, b):
        await self.handle_turn(i, b)

    @button(label="‎", row=3, index=7)
    async def eight(self, i, b):
        await self.handle_turn(i, b)

    @button(label="‎", row=3, index=8)
    async def nine(self, i, b):
        await self.handle_turn(i, b)
    
    async def start_game(self, i):
        player = get_member(self.get_current_player())
        respond_interaction(i, content=f"{player.mention}'s turn", component=self)

view = GameView()
await view.start_game(interaction)

exit_code = await view.wait(scope=scope, timeout=60)

if exit_code == "timeout":
    set_error("The board has been abandoned.")

winner = None
if view.winner:
    winner = get_member(view.winner)

message = f"The winner is {winner.mention} 🎉" if winner else "It's a draw"

And finally in the Response section of the command we can put {message} and create the command

Example

example

This is how you can crate a multiplayer tic-tac-toe game using discord custom commands bot

Loading comments…

{}

Need help?

Have a question, a suggestion, or stuck on something? Reach out — we're happy to help.

DiscordJoin support server
logoCustom Commands

The #1 custom commands Discord bot — build commands, events and databases with zero boilerplate.

stripe

Links

HomeBlogPrivacySupport

Contact

[email protected]

2 Frederick StreetLondon, WC1X 0ND

{ / } custom commands

© WEiRDSOFT LTD. All rights reserved.