Switch game to handle daytime smoother allowing cancellation

pull/147/head
bobloy 4 years ago
parent cb0a7f1041
commit 61d1313411

@ -65,7 +65,7 @@ class Game:
self.started = False self.started = False
self.game_over = False self.game_over = False
self.can_vote = False self.any_votes_remaining = False
self.used_votes = 0 self.used_votes = 0
self.day_time = False self.day_time = False
@ -88,6 +88,7 @@ class Game:
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.action_queue = deque() self.action_queue = deque()
self.current_action = None
self.listeners = {} self.listeners = {}
# def __del__(self): # def __del__(self):
@ -278,8 +279,12 @@ class Game:
self.action_queue.append(self._at_day_start()) self.action_queue.append(self._at_day_start())
while self.action_queue: while self.action_queue and not self.game_over:
await self.action_queue.popleft() current_action = asyncio.create_task(self.action_queue.popleft())
try:
await current_action
except asyncio.CancelledError:
log.debug("Cancelled task")
# #
# await self._at_day_start() # await self._at_day_start()
# # Once cycle ends, this will trigger end_game # # Once cycle ends, this will trigger end_game
@ -299,52 +304,57 @@ class Game:
if self.game_over: if self.game_over:
return return
self.action_queue.append(self._at_day_end()) # Get this ready in case day is cancelled
def check(): def check():
return not self.can_vote or not self.day_time or self.game_over return not self.any_votes_remaining or not self.day_time or self.game_over
self.day_count += 1 self.day_count += 1
# Print the results of who died during the night
embed = discord.Embed(title=random.choice(self.morning_messages).format(self.day_count)) embed = discord.Embed(title=random.choice(self.morning_messages).format(self.day_count))
for result in self.night_results: for result in self.night_results:
embed.add_field(name=result, value="________", inline=False) embed.add_field(name=result, value="________", inline=False)
self.day_time = True self.day_time = True # True while day
self.night_results = [] # Clear for next day self.night_results = [] # Clear for next day
await self.village_channel.send(embed=embed) await self.village_channel.send(embed=embed)
await self.generate_targets(self.village_channel) await self.generate_targets(self.village_channel) # Print remaining players for voting
await self.day_perms(self.village_channel) await self.day_perms(self.village_channel)
await self._notify("at_day_start") await self._notify("at_day_start") # Wait for day_start actions
await self._check_game_over() await self._check_game_over()
if self.game_over: if self.game_over: # If game ended because of _notify
return return
self.can_vote = True
self.any_votes_remaining = True
# Now we sleep and let the day happen. Print the remaining daylight half way through
await asyncio.sleep(HALF_DAY_LENGTH) # 4 minute days FixMe to 120 later await asyncio.sleep(HALF_DAY_LENGTH) # 4 minute days FixMe to 120 later
if check(): if check():
return return
await self.village_channel.send( await self.village_channel.send(
embed=discord.Embed(title="**Two minutes of daylight remain...**") embed=discord.Embed(title=f"**{HALF_DAY_LENGTH/60} minutes of daylight remain...**")
) )
await asyncio.sleep(HALF_DAY_LENGTH) # 4 minute days FixMe to 120 later await asyncio.sleep(HALF_DAY_LENGTH) # 4 minute days FixMe to 120 later
# Need a loop here to wait for trial to end (can_vote?) # Need a loop here to wait for trial to end
while self.ongoing_vote: while self.ongoing_vote:
await asyncio.sleep(5) await asyncio.sleep(5)
if check(): # Abruptly ends, assuming _day_end is next in queue
return
self.action_queue.append(self._at_day_end())
async def _at_voted(self, target): # ID 2 async def _at_voted(self, target): # ID 2
if self.game_over: if self.game_over:
return return
data = {"player": target}
# Notify that a target has been chosen
await self._notify("at_voted", player=target) await self._notify("at_voted", player=target)
# TODO: Support pre-vote target modifying roles
self.ongoing_vote = True self.ongoing_vote = True
self.used_votes += 1 self.used_votes += 1
@ -359,7 +369,7 @@ class Game:
await self.speech_perms(self.village_channel, target.member, undo=True) # No one can talk await self.speech_perms(self.village_channel, target.member, undo=True) # No one can talk
message: discord.Message = await self.village_channel.send( vote_message: discord.Message = await self.village_channel.send(
f"Everyone will now vote whether to lynch {target.mention}\n" f"Everyone will now vote whether to lynch {target.mention}\n"
"👍 to save, 👎 to lynch\n" "👍 to save, 👎 to lynch\n"
"*Majority rules, no-lynch on ties, " "*Majority rules, no-lynch on ties, "
@ -367,41 +377,47 @@ class Game:
allowed_mentions=discord.AllowedMentions(everyone=False, users=[target]), allowed_mentions=discord.AllowedMentions(everyone=False, users=[target]),
) )
await message.add_reaction("👍") await vote_message.add_reaction("👍")
await message.add_reaction("👎") await vote_message.add_reaction("👎")
await asyncio.sleep(15) await asyncio.sleep(15)
reaction_list = message.reactions reaction_list = vote_message.reactions
if True: # TODO: Allow customizable vote history deletion.
await vote_message.delete()
up_votes = sum(p for p in reaction_list if p.emoji == "👍" and not p.me) raw_up_votes = sum(p for p in reaction_list if p.emoji == "👍" and not p.me)
down_votes = sum(p for p in reaction_list if p.emoji == "👎" and not p.me) raw_down_votes = sum(p for p in reaction_list if p.emoji == "👎" and not p.me)
if down_votes > up_votes: # TODO: Support vote count modifying roles. (Need notify and count function)
embed = discord.Embed(title="Vote Results", color=0xFF0000) voted_to_lynch = raw_down_votes > raw_up_votes
if voted_to_lynch:
embed = discord.Embed(
title="Vote Results",
description=f"**Voted to lynch {target.mention}!**",
color=0xFF0000,
)
else: else:
embed = discord.Embed(title="Vote Results", color=0x80FF80) embed = discord.Embed(
title="Vote Results",
description=f"**{target.mention} has been spared!**",
color=0x80FF80,
)
embed.add_field(name="👎", value=f"**{up_votes}**", inline=True) embed.add_field(name="👎", value=f"**{raw_up_votes}**", inline=True)
embed.add_field(name="👍", value=f"**{down_votes}**", inline=True) embed.add_field(name="👍", value=f"**{raw_down_votes}**", inline=True)
await self.village_channel.send(embed=embed) await self.village_channel.send(embed=embed)
if down_votes > up_votes: if voted_to_lynch:
await self.village_channel.send(
f"**Voted to lynch {target.mention}!**",
allowed_mentions=discord.AllowedMentions(everyone=False, users=[target]),
)
await self.lynch(target) await self.lynch(target)
self.can_vote = False self.any_votes_remaining = False
else: else:
await self.village_channel.send(
f"**{target.mention} has been spared!**",
allowed_mentions=discord.AllowedMentions(everyone=False, users=[target]),
)
if self.used_votes >= self.day_vote_count: if self.used_votes >= self.day_vote_count:
await self.village_channel.send("**All votes have been used! Day is now over!**") await self.village_channel.send("**All votes have been used! Day is now over!**")
self.can_vote = False self.any_votes_remaining = False
else: else:
await self.village_channel.send( await self.village_channel.send(
f"**{self.used_votes}**/**{self.day_vote_count}** of today's votes have been used!\n" f"**{self.used_votes}**/**{self.day_vote_count}** of today's votes have been used!\n"
@ -410,21 +426,19 @@ class Game:
self.ongoing_vote = False self.ongoing_vote = False
if not self.can_vote: if not self.any_votes_remaining and self.day_time:
self.action_queue.append(self._at_day_end()) self.current_action.cancel()
else: else:
await self.normal_perms(self.village_channel) # No point if about to be night await self.normal_perms(self.village_channel) # No point if about to be night
async def _at_kill(self, target): # ID 3 async def _at_kill(self, target): # ID 3
if self.game_over: if self.game_over:
return return
data = {"player": target}
await self._notify("at_kill", player=target) await self._notify("at_kill", player=target)
async def _at_hang(self, target): # ID 4 async def _at_hang(self, target): # ID 4
if self.game_over: if self.game_over:
return return
data = {"player": target}
await self._notify("at_hang", player=target) await self._notify("at_hang", player=target)
async def _at_day_end(self): # ID 5 async def _at_day_end(self): # ID 5
@ -433,7 +447,7 @@ class Game:
if self.game_over: if self.game_over:
return return
self.can_vote = False self.any_votes_remaining = False
self.day_vote = {} self.day_vote = {}
self.vote_totals = {} self.vote_totals = {}
self.day_time = False self.day_time = False
@ -476,7 +490,6 @@ class Game:
async def _at_visit(self, target, source): # ID 8 async def _at_visit(self, target, source): # ID 8
if self.game_over: if self.game_over:
return return
data = {"target": target, "source": source}
await self._notify("at_visit", target=target, source=source) await self._notify("at_visit", target=target, source=source)
async def _notify(self, event, **kwargs): async def _notify(self, event, **kwargs):
@ -484,6 +497,8 @@ class Game:
tasks = [] tasks = []
for event in self.listeners.get(event, {}).get(i, []): for event in self.listeners.get(event, {}).get(i, []):
tasks.append(asyncio.ensure_future(event(**kwargs), loop=self.loop)) tasks.append(asyncio.ensure_future(event(**kwargs), loop=self.loop))
# Run same-priority task simultaneously
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
# self.bot.dispatch(f"red.fox.werewolf.{event}", data=data, priority=i) # self.bot.dispatch(f"red.fox.werewolf.{event}", data=data, priority=i)
@ -507,8 +522,7 @@ class Game:
async def generate_targets(self, channel, with_roles=False): async def generate_targets(self, channel, with_roles=False):
embed = discord.Embed(title="Remaining Players") embed = discord.Embed(title="Remaining Players")
for i in range(len(self.players)): for i, player in enumerate(self.players):
player = self.players[i]
if player.alive: if player.alive:
status = "" status = ""
else: else:
@ -653,7 +667,7 @@ class Game:
return return
if channel == self.village_channel: if channel == self.village_channel:
if not self.can_vote: if not self.any_votes_remaining:
await channel.send("Voting is not allowed right now") await channel.send("Voting is not allowed right now")
return return
elif channel.name in self.p_channels: elif channel.name in self.p_channels:
@ -695,13 +709,8 @@ class Game:
if self.vote_totals[target_id] < required_votes: if self.vote_totals[target_id] < required_votes:
await self.village_channel.send( await self.village_channel.send(
"" f"{author.mention} has voted to put {target.member.mention} to trial. "
"{} has voted to put {} to trial. " f"{required_votes - self.vote_totals[target_id]} more votes needed",
"{} more votes needed".format(
author.mention,
target.member.mention,
required_votes - self.vote_totals[target_id],
),
allowed_mentions=discord.AllowedMentions(everyone=False, users=[author, target]), allowed_mentions=discord.AllowedMentions(everyone=False, users=[author, target]),
) )
else: else:
@ -771,8 +780,8 @@ class Game:
Attempt to lynch a target Attempt to lynch a target
Important to finish execution before triggering notify Important to finish execution before triggering notify
""" """
target = await self.get_day_target(target_id) target = await self.get_day_target(target_id) # Allows target modification
target.alive = False target.alive = False # Kill them,
await self._at_hang(target) await self._at_hang(target)
if not target.alive: # Still dead after notifying if not target.alive: # Still dead after notifying
await self.dead_perms(self.village_channel, target.member) await self.dead_perms(self.village_channel, target.member)

Loading…
Cancel
Save