Home > Net >  Discord.py turn system
Discord.py turn system

Time:08-15

I'm currently developing a message-based discord.py turn system, which stores player data in an aiosqlite database (not shown here) and later utilizes that data to figure out whose turn it currently is; although, things didn't really go as planned...

What my code is supposed to do is reuse data from the database and sort it into lists of a maximum of four (each text channel is allowed to have a maximum of four players and a minimum of two players taking turns at a game at any instance in time); so I made a formatter, which basically grabs the players (ranging from 2 to 4) and puts them in a list, which is passed into the designated player arguments within the Turns class - and then (with the help of the other classes), spit out an appropriate value of whose turn it currently is (controlled by the defined functions).

Here's my Cog file:

(Do note that this is a snippet of my full code - if you see any unused variables or missing imports, know that this wasn't a mistake on my side).

Long code ahead*

from discord.ext import commands

class Turns:
    def __init__(self, player1: int = None, player2: int = None, player3: int = None, player4: int = None):
        self.player_1 = player1
        self.player_2 = player2
        self.player_3 = player3
        self.player_4 = player4
        self.turn = 1
        self.turn_member = self.player_1

    def switch_turns(self, player: int):

        if self.player_4 is not None:
            if player == self.player_1:
                self.turn = 2
                self.turn_member = self.player_2
                return self.player_2, self.turn, self.turn_member
            if player == self.player_2:
                self.turn = 3
                self.turn_member = self.player_3
                return self.player_3, self.turn, self.turn_member
            if player == self.player_3:
                self.turn = 4
                self.turn_member = self.player_4
                return self.player_4, self.turn, self.turn_member
            if player == self.player_4:
                self.turn = 1
                self.turn_member = self.player_1
                return self.player_1, self.turn, self.turn_member

        if self.player_3 is not None and self.player_4 is None:
            if player == self.player_1:
                self.turn = 2
                self.turn_member = self.player_2
                return self.player_2, self.turn, self.turn_member
            if player == self.player_2:
                self.turn = 3
                self.turn_member = self.player_3
                return self.player_3, self.turn, self.turn_member
            if player == self.player_3:
                self.turn = 1
                self.turn_member = self.player_2
                return self.player_1, self.turn, self.turn_member

        if self.player_4 is None and self.player_3 is None:
            if player == self.player_1:
                self.turn = 2
                self.turn_member = self.player_2
                return self.player_2, self.turn, self.turn_member
            if player == self.player_2:
                self.turn = 1
                self.turn_member = self.player_1
                return self.player_1, self.turn, self.turn_member

    def player_current_turn(self):

        if self.player_4 is not None:
            if self.turn == 1:
                return self.player_1
            if self.turn == 2:
                return self.player_2
            if self.turn == 3:
                return self.player_3
            if self.turn == 4:
                return self.player_4

        if self.player_3 is not None and self.player_4 is None:
            if self.turn == 1:
                return self.player_1
            if self.turn == 2:
                return self.player_2
            if self.turn == 3:
                return self.player_3

        if self.player_4 is None and self.player_3 is None:
            if self.turn == 1:
                return self.player_1
            if self.turn == 2:
                return self.player_2


class Format_Lists:

    @staticmethod
    def format_list(list: list = None):
        if len(list) == 4:
            pass
        if len(list) == 3:
            formatted_triple_list = [list[0], list[1], list[2], None]
            return formatted_triple_list
        if len(list) == 2:
            formatted_double_list = [list[0], list[1], None, None]
            return formatted_double_list


class RoleplayModerationCoVents(commands.Cog):

    def turn_set(self, player1: int = None, player2: int = None, player3: int = None, player4: int = None):
        if player3 is None and player4 is None:
            turns = Turns(player1=player1, player2=player2)
            return turns
        if player4 is None and player3 is not None:
            turns = Turns(player1=player1, player2=player2, player3=player3)
            return turns
        if player4 is not None:
            turns = Turns(player1=player1, player2=player2, player3=player3, player4=player4)
            return turns

    @commands.Cog.listener()
    async def on_message(self, ctx):

        FormattedList = Format_Lists().format_list({list acquired from database}) # A list of length 4 containing the players' IDs, with None in each empty entry.

        turns = self.turn_set(player1=FormattedList[0], player2=FormattedList[1], player3=FormattedList[2], player4=FormattedList[3])
        print(f'It\'s currently {await ctx.guild.fetch_member(turns.player_current_turn())}\'s turn!')

        if ctx.author.id == turns.player_current_turn():
            turns.switch_turns(player=ctx.author.id)
            new_turn = int(turns.player_current_turn())
            print(f'It\'s now {await ctx.guild.fetch_member(new_turn)}\'s turn!')
            return int(turns.player_current_turn())

async def setup(bot):
    await bot.add_cog(RoleplayModerationCoVents(bot))

I'll be using a two-player dynamic in this example for the sake of simplicity.

Now, the problem is that my code has a bug; in which it seems to work fine at first - displaying the intended:

It's currently {insert player1}'s turn!

It's now {insert player2}'s turn!

However, when I send another text message within the text channel - the code simply repeats and reprints the two lines above...

What I intended to have as an output here is:

'It's currently {insert player2}'s turn!'

'It's now {insert player1}'s turn!'

I pretty much want the on_message event to return the new player and make that change instance-wise.

If anyone could help, please do supply me with some information and code! I'd love to know how I could fix this!

Please also mind that I'm not super experienced with classes and the intricacies behind Python; if possible, please explain things thoroughly!

Thank you in advance.

CodePudding user response:

The problem is, you're calling self.turn_set() and store the instance of Turns inside the on_message function. After the function is finished the data is lost.

Simple example:

def foo():
    count = 0
    print(count)
    count  = 1   # change variable here

foo()
foo()

# Output
0
0    # no change, because 'count' gets reseted

In your case, foo is the on_message event, count is turns and 0 your Turns object.

You need to check if the game has started and store the instance elsewhere. Let's say we have the same situation as above, but now the bug is solved:

class FooClass:

    def __init__(self):
        self.is_started = False
        self.count = None

    def foo(self):
        if not self.is_started:   #   the conditions you want that the game starts
            self.is_started = True
            self.count = 0   # store 0 only on first call
        else:
            self.count  = 1

        # when game is finished, reset flag
        if self.count == 2:
            self.is_started = False

        print(self.count)

foo_object = FooClass()
foo_object.foo()
foo_object.foo()
foo_object.foo()
foo_object.foo()

# Output
0
1
2
0   # reset

In this case I said, I want to finish it when count reaches the value 2.

In your case you can store these 2 variables in your RoleplayModerationCoVents class like so:

# ...

class RoleplayModerationCoVents(commands.Cog):
    
    def __init__(self, bot):
        self.bot = bot
        self.is_started = False
        self.turns = None
        
    def turn_set(self, player1: int = None, player2: int = None, player3: int = None, player4: int = None):
        # ...

    @commands.Cog.listener()
    async def on_message(self, ctx):
        # ...
        if not self.is_started:   #   the conditions you want that the game starts
            # start game by setting bool to True
            self.is_started = True
            self.turns = self.turn_set(player1=FormattedList[0], player2=FormattedList[1], player3=FormattedList[2], player4=FormattedList[3])
            print(f'It\'s currently {await ctx.guild.fetch_member(turns.player_current_turn())}\'s turn!')

        if ctx.author.id == turns.player_current_turn():
            self.turns.switch_turns(player=ctx.author.id)
            new_turn = int(self.turns.player_current_turn())
            print(f'It\'s now {await ctx.guild.fetch_member(new_turn)}\'s turn!')
            # ...
        
        if ... : # conditions which end the game
            self.is_started = False
async def setup(bot):
    await bot.add_cog(RoleplayModerationCoVents(bot))

CodePudding user response:

To add on @puncher's post, here's a final, fully operational code:

from discord.ext import commands


class Turns:
    def __init__(self, player1: int = None, player2: int = None, player3: int = None, player4: int = None):
        self.player_1 = player1
        self.player_2 = player2
        self.player_3 = player3
        self.player_4 = player4
        self.turn = 1
        self.turn_member = self.player_1

    def switch_turns(self, player: int):

        if self.player_4 is not None:
            if player == self.player_1:
                self.turn = 2
                self.turn_member = self.player_2
                return self.player_2, self.turn, self.turn_member
            if player == self.player_2:
                self.turn = 3
                self.turn_member = self.player_3
                return self.player_3, self.turn, self.turn_member
            if player == self.player_3:
                self.turn = 4
                self.turn_member = self.player_4
                return self.player_4, self.turn, self.turn_member
            if player == self.player_4:
                self.turn = 1
                self.turn_member = self.player_1
                return self.player_1, self.turn, self.turn_member

        if self.player_3 is not None and self.player_4 is None:
            if player == self.player_1:
                self.turn = 2
                self.turn_member = self.player_2
                return self.player_2, self.turn, self.turn_member
            if player == self.player_2:
                self.turn = 3
                self.turn_member = self.player_3
                return self.player_3, self.turn, self.turn_member
            if player == self.player_3:
                self.turn = 1
                self.turn_member = self.player_2
                return self.player_1, self.turn, self.turn_member

        if self.player_4 is None and self.player_3 is None:
            if player == self.player_1:
                self.turn = 2
                self.turn_member = self.player_2
                return self.player_2, self.turn, self.turn_member
            if player == self.player_2:
                self.turn = 1
                self.turn_member = self.player_1
                return self.player_1, self.turn, self.turn_member

    def player_current_turn(self):

        if self.player_4 is not None:
            if self.turn == 1:
                return self.player_1
            if self.turn == 2:
                return self.player_2
            if self.turn == 3:
                return self.player_3
            if self.turn == 4:
                return self.player_4

        if self.player_3 is not None and self.player_4 is None:
            if self.turn == 1:
                return self.player_1
            if self.turn == 2:
                return self.player_2
            if self.turn == 3:
                return self.player_3

        if self.player_4 is None and self.player_3 is None:
            if self.turn == 1:
                return self.player_1
            if self.turn == 2:
                return self.player_2

    def rp(self):
        if not self.is_started:
            self.is_started = True
            self.count = 0
        else:
            self.count  = 1


class Format_Lists:

    @staticmethod
    def format_list(list: list = None):
        if len(list) == 4:
            pass
        if len(list) == 3:
            formatted_triple_list = [list[0], list[1], list[2], None]
            return formatted_triple_list
        if len(list) == 2:
            formatted_double_list = [list[0], list[1], None, None]
            return formatted_double_list


class RoleplayModerationCoVents(commands.Cog):

    def __init__(self, bot):
        self.bot = bot
        self.is_started = False
        self.turns = None

    def turn_set(self, player1: int = None, player2: int = None, player3: int = None, player4: int = None):
        if player3 is None and player4 is None:
            turn = Turns(player1=player1, player2=player2)
            return turn
        if player4 is None and player3 is not None:
            turn = Turns(player1=player1, player2=player2, player3=player3)
            return turn
        if player4 is not None:
            turn = Turns(player1=player1, player2=player2, player3=player3, player4=player4)
            return turn

    @commands.Cog.listener()
    async def on_message(self, ctx):

        if not self.is_started:   #   the conditions you want that the game starts
            # start game by setting bool to True
            self.is_started = True
            self.turns = self.turn_set(player1=FormattedList[0], player2=FormattedList[1], player3=FormattedList[2], player4=FormattedList[3])
            print(f'It\'s currently {await ctx.guild.fetch_member(self.turns.player_current_turn())}\'s turn!')

        if ctx.author.id == self.turns.player_current_turn():
            self.turns.switch_turns(player=ctx.author.id)
            new_turn = int(self.turns.player_current_turn())
            print(f'It\'s now {await ctx.guild.fetch_member(new_turn)}\'s turn!')
            return
            # ...

        if ... : # conditions which end the game
            self.is_started = False


async def setup(bot):
    await bot.add_cog(RoleplayModerationCoVents(bot))

I've added a bare return statement after the line containing print(f'It\'s now {await ctx.guild.fetch_member(new_turn)}\'s turn!'), since the code wasn't actually returning the new player and was instead stuck on the first player.

Massive thanks to @puncher for writing this code; and for whoever stumbles upon this in the future - you're a lucky one :)

  • Related