Implemented veto power
This commit is contained in:
parent
d06954b1a3
commit
b6184624f8
|
|
@ -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):
|
||||
|
|
|
|||
26
SecretBot.py
26
SecretBot.py
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue