Implemented veto power

This commit is contained in:
Elnath 2021-06-14 00:43:11 +02:00
parent d06954b1a3
commit b6184624f8
2 changed files with 99 additions and 31 deletions

View File

@ -50,6 +50,20 @@ def vote_running(func):
return decorated
def policies_drawn(func):
"""
Decorator for *methods* of Game that need policies to have been drawn (i.e. the legislative phase has started)
"""
@wraps(func)
def decorated(obj: 'Game', *args, **kwargs):
if obj.config["drawn"] is None:
raise RuntimeError("This function only works when policies have been drawn (i.e. the legislative phase has started)")
return func(obj, *args, **kwargs)
return decorated
def save_on_success(func):
"""
Decorator for *async methods* of game that calls the save function after the method has executed without exceptions
@ -191,6 +205,14 @@ class Game:
def get_player_channel(self, player: Union[int, discord.Member]) -> discord.TextChannel:
return self.guild.get_channel(self.get_player_channel_id(player))
@game_started
def is_legislative_phase(self) -> bool:
return self.config["drawn"] is not None
@game_started
def get_enacted_policies(self) -> List[Policy]:
return [Policy(policy_str) for policy_str in self.config["enacted"]]
@save_on_success
async def start(self, player_role: discord.Role, bot_user_id: int):
if self.is_started():
@ -371,11 +393,10 @@ class Game:
@save_on_success
async def stop_vote(self):
logging.debug(f"[{self.guild.name}] Stopping the vote")
tasks = []
passed = self.is_vote_passing()
self.config["vote"]["revealed"] = True
await self.update_vote_message()
tasks.append(asyncio.create_task(self.get_votes_channel().send("**The vote has ended**")))
await self.get_votes_channel().send("**The vote has ended**")
announcement_content = [
f"<@&{self.get_player_role_id()}> <@&{self.get_gm_role_id()}> the vote has ended!",
f"{':green_square:' if passed else ':red_square:'} The vote has **{'' if passed else 'not '}passed**"
@ -387,25 +408,32 @@ class Game:
if self.config["chaos"] > 0: # If there was some chaos
announcement_content.append(":relaxed: The country has calmed and the chaos counter has been reset")
self.config["chaos"] = 0 # Anyway, the chaos is reset by a successful vote
else:
chaos = self.config["chaos"] + 1
if chaos < 3:
self.config["chaos"] = chaos
announcement_content.append(
":fire: The country slowly descends into chaos " + " ".join([":fire:" for _ in range(chaos)] + [":black_small_square:" for _ in range(3 - chaos)])
)
else: # Too many rejected votes throw the country into chaos
announcement_content.append(":fire: :fire: :fire: **The country is thrown into chaos by too many rejected votes** :fire: :fire: :fire:")
tasks.append(asyncio.create_task(self.enact_top_policy(delay = 10)))
tasks.append(asyncio.create_task(self.get_announcements_channel().send("\n".join(announcement_content), allowed_mentions = discord.AllowedMentions(roles = True))))
await asyncio.wait(tasks)
await self.get_announcements_channel().send("\n".join(announcement_content), allowed_mentions = discord.AllowedMentions(roles = True))
self.config["vote"] = None
# After announcing a non-passing vote, we increase the chaos and announce it
if not passed:
await self.increase_chaos()
@game_started
@save_on_success
async def increase_chaos(self):
new_chaos = self.config["chaos"] + 1
if new_chaos < 3:
self.config["chaos"] = new_chaos
await self.get_announcements_channel().send(
":fire: The country slowly descends into chaos " + " ".join([":fire:" for _ in range(new_chaos)] + [":black_small_square:" for _ in range(3 - new_chaos)])
)
else: # Chaos is too high!
await self.get_announcements_channel().send(":fire: :fire: :fire: **The country is thrown into chaos** :fire: :fire: :fire:")
await self.enact_top_policy(delay = 10)
await self.get_announcements_channel().send(":relaxed: The country has calmed and the chaos counter has been reset")
self.config["chaos"] = 0 # We reset the counter
@game_started
@save_on_success
async def draw_policies(self) -> List[Policy]:
if self.is_legislative_phase():
raise RuntimeError("Can not draw cards if some are already in hand. Enact one or veto instead.")
self.config["drawn"] = [self.config["deck"].pop(0) for _ in range(3)]
return [Policy(p) for p in self.config["drawn"]]
@ -414,10 +442,9 @@ class Game:
return [Policy(self.config["deck"][i]) for i in range(3)]
@game_started
@policies_drawn
@save_on_success
async def enact_drawn_policy(self, index: int):
if self.config["drawn"] is None:
raise RuntimeError("Can only enact a policy when they have been drawn")
if not (0 <= index < len(self.config["drawn"])):
raise IndexError(f"Expected policy index between 0 and {len(self.config['drawn'])}, got {index}")
for i, policy_str in enumerate(self.config["drawn"]):
@ -427,12 +454,12 @@ class Game:
self.config["discard"].append(policy_str)
self.config["drawn"] = None
if len(self.config["deck"]) < 3:
self.shuffle_discard_into_deck()
await self.shuffle_discard_into_deck()
await self.announce_latest_enacted_policy()
@game_started
@save_on_success
def shuffle_discard_into_deck(self):
async def shuffle_discard_into_deck(self):
self.config["deck"].extend(self.config["discard"])
self.config["discard"] = []
random.shuffle(self.config["deck"])
@ -442,26 +469,41 @@ class Game:
async def enact_top_policy(self, delay = 10):
logger.debug(f"[{self.guild.name}] Enacting top policy in {delay} seconds...")
await asyncio.sleep(delay)
self.config["chaos"] = 0 # We reset the counter
self.config["enacted"].append(self.config["deck"].pop(0))
if len(self.config["deck"]) < 3:
self.shuffle_discard_into_deck()
await self.shuffle_discard_into_deck()
await self.announce_latest_enacted_policy()
@game_started
async def announce_latest_enacted_policy(self):
last_enacted = Policy(self.config["enacted"][-1])
enacted_count = defaultdict(int)
for policy_str in self.config["enacted"]:
enacted_count[Policy(policy_str)] += 1
for policy in self.get_enacted_policies():
enacted_count[policy] += 1
message_content = [
f"{self.get_player_role().mention} A **{last_enacted.name}** policy {last_enacted.square_emoji()} has been enacted!",
f"In total, **{enacted_count[Policy.LIBERAL]} {Policy.LIBERAL.name}** policies and **{enacted_count[Policy.FASCIST]} {Policy.FASCIST.name}** policies have been enacted",
" ".join([Policy.LIBERAL.square_emoji()] * enacted_count[Policy.LIBERAL] + [":black_small_square:"] * (5 - enacted_count[Policy.LIBERAL])),
" ".join([Policy.FASCIST.square_emoji()] * enacted_count[Policy.FASCIST] + [":black_small_square:"] * (6 - enacted_count[Policy.FASCIST])),
]
if last_enacted == Policy.FASCIST:
if enacted_count[Policy.FASCIST] == 3:
message_content.append("Be careful about who you elect as chancellor!")
elif enacted_count[Policy.FASCIST] == 5:
message_content.append(":person_gesturing_no: Veto power unlocked :person_gesturing_no:")
await self.get_announcements_channel().send("\n".join(message_content), allowed_mentions = discord.AllowedMentions(roles = True))
@game_started
@policies_drawn
@save_on_success
async def veto(self):
self.config["discard"].extend(self.config["drawn"])
self.config["drawn"] = None
if len(self.config["deck"]) < 3:
await self.shuffle_discard_into_deck()
await self.get_announcements_channel().send(f"<@&{self.get_player_role_id()}>\n:person_gesturing_no: The government used the veto power! No policies have been enacted. :person_gesturing_no:", allowed_mentions = discord.AllowedMentions(roles = True))
await self.increase_chaos()
@game_started
@save_on_success
async def kill_player(self, player: discord.Member):

View File

@ -261,6 +261,9 @@ class SecretBot(commands.Cog):
game = await self.get_running_game_or_error_message(ctx)
await self.check_is_administrator_or_gm(ctx)
await self.check_in_admin_channel_or_error_message(ctx, game)
if game.is_legislative_phase():
await ctx.reply(":x: The game is already in a legislative phase. Enact a policy or use veto power")
else:
policies = await game.draw_policies()
message_content = [
"The following policies have been drawn:",
@ -303,6 +306,29 @@ class SecretBot(commands.Cog):
await game.kill_player(player)
await ctx.reply(":dagger: The order has been executed.")
@commands.command("Veto")
async def veto_policy_with_confirmation_if_veto_locked(self, ctx: commands.Context):
game = await self.get_running_game_or_error_message(ctx)
await self.check_is_administrator_or_gm(ctx)
if game.is_legislative_phase():
nb_fascist_policies = len([policy for policy in game.get_enacted_policies() if policy == Policy.FASCIST])
if nb_fascist_policies < 5:
await self.confirm_action(
f"Are you sure that you want to use the veto power with less than 5 enacted fascist policies? ({nb_fascist_policies} enacted)",
ctx.channel,
self.veto_policy(ctx),
ctx.message
)
else:
await self.veto_policy(ctx)
else:
await ctx.reply(":x: We are not in a legislative phase")
async def veto_policy(self, ctx: commands.Context):
game = await self.get_running_game_or_error_message(ctx)
await self.check_is_administrator_or_gm(ctx)
await game.veto()
if __name__ == '__main__':
argparser = argparse.ArgumentParser(description = "Secret Hitler helper bot", formatter_class = argparse.ArgumentDefaultsHelpFormatter)