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 :)