Search code examples
pythondiscord.pytic-tac-toe

Discord.py V2.0 Tictactoe command only for two players


I'm using discord.py 2.0 for the first time and I can't find a solution to change this code for a TicTacToe singleplayer game to multiplayer. I want to be able to write /tictactoe {member} and that you only can place your mark when it's your turn. At the moment any member can place a mark at anytime. The code is from Rapptz I only changed the command to a slash command. Thanks in advance! :D

EDIT: I found a solution and it is in the comments!

from typing import List
from discord.ext import commands
import discord
from discord import app_commands


class TicTacToeButton(discord.ui.Button['TicTacToe']):
    def __init__(self, x: int, y: int):
        super().__init__(style=discord.ButtonStyle.secondary, label='\u200b', row=y)
        self.x = x
        self.y = y

    # This function is called whenever this particular button is pressed
    # This is part of the "meat" of the game logic
    async def callback(self, interaction: discord.Interaction):
        assert self.view is not None
        view: TicTacToe = self.view
        state = view.board[self.y][self.x]
        if state in (view.X, view.O):
            return

        if view.current_player == view.X:
            self.style = discord.ButtonStyle.danger
            self.label = 'X'
            self.disabled = True
            view.board[self.y][self.x] = view.X
            view.current_player = view.O
            content = "It is now O's turn"
        else:
            self.style = discord.ButtonStyle.success
            self.label = 'O'
            self.disabled = True
            view.board[self.y][self.x] = view.O
            view.current_player = view.X
            content = "It is now X's turn"

        winner = view.check_board_winner()
        if winner is not None:
            if winner == view.X:
                content = 'X won!'
            elif winner == view.O:
                content = 'O won!'
            else:
                content = "It's a tie!"

            for child in view.children:
                child.disabled = True

            view.stop()

        await interaction.response.edit_message(content=content, view=view)


# This is our actual board View
class TicTacToe(discord.ui.View):
    # This tells the IDE or linter that all our children will be TicTacToeButtons
    # This is not required
    children: List[TicTacToeButton]
    X = -1
    O = 1
    Tie = 2

    def __init__(self):
        super().__init__()
        self.current_player = self.X
        self.board = [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
        ]

        # Our board is made up of 3 by 3 TicTacToeButtons
        # The TicTacToeButton maintains the callbacks and helps steer
        # the actual game.
        for x in range(3):
            for y in range(3):
                self.add_item(TicTacToeButton(x, y))

    # This method checks for the board winner -- it is used by the TicTacToeButton
    def check_board_winner(self):
        for across in self.board:
            value = sum(across)
            if value == 3:
                return self.O
            elif value == -3:
                return self.X

        # Check vertical
        for line in range(3):
            value = self.board[0][line] + self.board[1][line] + self.board[2][line]
            if value == 3:
                return self.O
            elif value == -3:
                return self.X

        # Check diagonals
        diag = self.board[0][2] + self.board[1][1] + self.board[2][0]
        if diag == 3:
            return self.O
        elif diag == -3:
            return self.X

        diag = self.board[0][0] + self.board[1][1] + self.board[2][2]
        if diag == 3:
            return self.O
        elif diag == -3:
            return self.X

        # If we're here, we need to check if a tie was made
        if all(i != 0 for row in self.board for i in row):
            return self.Tie

        return None






class tictactoe(commands.Cog):
    def __init__(self, client):
        self.client = client

    @app_commands.command(
        name="tictactoe",
        description="play tictactoe"
    )
    async def tictactoe(self,interaction: discord.Interaction):
        await interaction.response.send_message('Tic Tac Toe: X goes first', view=TicTacToe())



async def setup(client: commands.Bot):
   await client.add_cog(tictactoe(client))

Solution

  • Ok i did it with less than 10 lines of code if anyone wants a tictactoe command here it is:

    from typing import List
    from discord.ext import commands
    import discord
    from discord import app_commands
    
    class TicTacToeButton(discord.ui.Button['TicTacToe']):
        def __init__(self, x: int, y: int):
            super().__init__(style=discord.ButtonStyle.secondary, label='\u200b', row=y)
            self.x = x
            self.y = y
    
        # This function is called whenever this particular button is pressed
        # This is part of the "meat" of the game logic
        async def callback(self, interaction: discord.Interaction):
            
            global player1
            global player2
    
            assert self.view is not None
            view: TicTacToe = self.view
            state = view.board[self.y][self.x]
            if state in (view.X, view.O):
                return
    
            if view.current_player == view.X:
                if interaction.user != player1:
                    await interaction.response.send_message("Its not your Turn!", ephemeral=True)
                else:
                    self.style = discord.ButtonStyle.danger
                    self.label = 'X'
                    self.disabled = True
                    view.board[self.y][self.x] = view.X
                    view.current_player = view.O
                    content = "It is now O's turn"
            
            else:
                if interaction.user != player2:
                    await interaction.response.send_message("Its not your Turn!", ephemeral=True)
                else:
                    self.style = discord.ButtonStyle.success
                    self.label = 'O'
                    self.disabled = True
                    view.board[self.y][self.x] = view.O
                    view.current_player = view.X
                    content = "It is now X's turn"
    
            winner = view.check_board_winner()
            if winner is not None:
                if winner == view.X:
                    content = 'X won!'
                elif winner == view.O:
                    content = 'O won!'
                else:
                    content = "It's a tie!"
    
                for child in view.children:
                    child.disabled = True
    
                view.stop()
    
            await interaction.response.edit_message(content=content, view=view)
    
    
    # This is our actual board View
    class TicTacToe(discord.ui.View):
        # This tells the IDE or linter that all our children will be TicTacToeButtons
        # This is not required
        children: List[TicTacToeButton]
        X = -1
        O = 1
        Tie = 2
    
        def __init__(self):
            super().__init__()
            self.current_player = self.X
            self.board = [
                [0, 0, 0],
                [0, 0, 0],
                [0, 0, 0],
            ]
    
            # Our board is made up of 3 by 3 TicTacToeButtons
            # The TicTacToeButton maintains the callbacks and helps steer
            # the actual game.
            for x in range(3):
                for y in range(3):
                    self.add_item(TicTacToeButton(x, y))
    
        # This method checks for the board winner -- it is used by the TicTacToeButton
        def check_board_winner(self):
            for across in self.board:
                value = sum(across)
                if value == 3:
                    return self.O
                elif value == -3:
                    return self.X
    
            # Check vertical
            for line in range(3):
                value = self.board[0][line] + self.board[1][line] + self.board[2][line]
                if value == 3:
                    return self.O
                elif value == -3:
                    return self.X
    
            # Check diagonals
            diag = self.board[0][2] + self.board[1][1] + self.board[2][0]
            if diag == 3:
                return self.O
            elif diag == -3:
                return self.X
    
            diag = self.board[0][0] + self.board[1][1] + self.board[2][2]
            if diag == 3:
                return self.O
            elif diag == -3:
                return self.X
    
            # If we're here, we need to check if a tie was made
            if all(i != 0 for row in self.board for i in row):
                return self.Tie
    
            return None
    
    
    
    
    
    
    class tictactoe(commands.Cog):
        def __init__(self, client):
            self.client = client
    
        @app_commands.command(
            name="tictactoe",
            description="play tictactoe"
        )
        async def tictactoe(self,interaction: discord.Interaction, enemy: discord.Member):
            await interaction.response.send_message('Tic Tac Toe: X goes first', view=TicTacToe())
            
            global player1
            global player2
    
            player1 = interaction.user
            player2 = enemy
    
    async def setup(client: commands.Bot):
       await client.add_cog(tictactoe(client))```