Implemented veto power
This commit is contained in:
parent
d06954b1a3
commit
b6184624f8
|
|
@ -50,6 +50,20 @@ def vote_running(func):
|
||||||
return decorated
|
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):
|
def save_on_success(func):
|
||||||
"""
|
"""
|
||||||
Decorator for *async methods* of game that calls the save function after the method has executed without exceptions
|
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:
|
def get_player_channel(self, player: Union[int, discord.Member]) -> discord.TextChannel:
|
||||||
return self.guild.get_channel(self.get_player_channel_id(player))
|
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
|
@save_on_success
|
||||||
async def start(self, player_role: discord.Role, bot_user_id: int):
|
async def start(self, player_role: discord.Role, bot_user_id: int):
|
||||||
if self.is_started():
|
if self.is_started():
|
||||||
|
|
@ -371,11 +393,10 @@ class Game:
|
||||||
@save_on_success
|
@save_on_success
|
||||||
async def stop_vote(self):
|
async def stop_vote(self):
|
||||||
logging.debug(f"[{self.guild.name}] Stopping the vote")
|
logging.debug(f"[{self.guild.name}] Stopping the vote")
|
||||||
tasks = []
|
|
||||||
passed = self.is_vote_passing()
|
passed = self.is_vote_passing()
|
||||||
self.config["vote"]["revealed"] = True
|
self.config["vote"]["revealed"] = True
|
||||||
await self.update_vote_message()
|
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 = [
|
announcement_content = [
|
||||||
f"<@&{self.get_player_role_id()}> <@&{self.get_gm_role_id()}> the vote has ended!",
|
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**"
|
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
|
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")
|
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
|
self.config["chaos"] = 0 # Anyway, the chaos is reset by a successful vote
|
||||||
|
await self.get_announcements_channel().send("\n".join(announcement_content), allowed_mentions = discord.AllowedMentions(roles = True))
|
||||||
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)
|
|
||||||
self.config["vote"] = None
|
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
|
@game_started
|
||||||
@save_on_success
|
@save_on_success
|
||||||
async def draw_policies(self) -> List[Policy]:
|
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)]
|
self.config["drawn"] = [self.config["deck"].pop(0) for _ in range(3)]
|
||||||
return [Policy(p) for p in self.config["drawn"]]
|
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)]
|
return [Policy(self.config["deck"][i]) for i in range(3)]
|
||||||
|
|
||||||
@game_started
|
@game_started
|
||||||
|
@policies_drawn
|
||||||
@save_on_success
|
@save_on_success
|
||||||
async def enact_drawn_policy(self, index: int):
|
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"])):
|
if not (0 <= index < len(self.config["drawn"])):
|
||||||
raise IndexError(f"Expected policy index between 0 and {len(self.config['drawn'])}, got {index}")
|
raise IndexError(f"Expected policy index between 0 and {len(self.config['drawn'])}, got {index}")
|
||||||
for i, policy_str in enumerate(self.config["drawn"]):
|
for i, policy_str in enumerate(self.config["drawn"]):
|
||||||
|
|
@ -427,12 +454,12 @@ class Game:
|
||||||
self.config["discard"].append(policy_str)
|
self.config["discard"].append(policy_str)
|
||||||
self.config["drawn"] = None
|
self.config["drawn"] = None
|
||||||
if len(self.config["deck"]) < 3:
|
if len(self.config["deck"]) < 3:
|
||||||
self.shuffle_discard_into_deck()
|
await self.shuffle_discard_into_deck()
|
||||||
await self.announce_latest_enacted_policy()
|
await self.announce_latest_enacted_policy()
|
||||||
|
|
||||||
@game_started
|
@game_started
|
||||||
@save_on_success
|
@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["deck"].extend(self.config["discard"])
|
||||||
self.config["discard"] = []
|
self.config["discard"] = []
|
||||||
random.shuffle(self.config["deck"])
|
random.shuffle(self.config["deck"])
|
||||||
|
|
@ -442,26 +469,41 @@ class Game:
|
||||||
async def enact_top_policy(self, delay = 10):
|
async def enact_top_policy(self, delay = 10):
|
||||||
logger.debug(f"[{self.guild.name}] Enacting top policy in {delay} seconds...")
|
logger.debug(f"[{self.guild.name}] Enacting top policy in {delay} seconds...")
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
self.config["chaos"] = 0 # We reset the counter
|
|
||||||
self.config["enacted"].append(self.config["deck"].pop(0))
|
self.config["enacted"].append(self.config["deck"].pop(0))
|
||||||
if len(self.config["deck"]) < 3:
|
if len(self.config["deck"]) < 3:
|
||||||
self.shuffle_discard_into_deck()
|
await self.shuffle_discard_into_deck()
|
||||||
await self.announce_latest_enacted_policy()
|
await self.announce_latest_enacted_policy()
|
||||||
|
|
||||||
@game_started
|
@game_started
|
||||||
async def announce_latest_enacted_policy(self):
|
async def announce_latest_enacted_policy(self):
|
||||||
last_enacted = Policy(self.config["enacted"][-1])
|
last_enacted = Policy(self.config["enacted"][-1])
|
||||||
enacted_count = defaultdict(int)
|
enacted_count = defaultdict(int)
|
||||||
for policy_str in self.config["enacted"]:
|
for policy in self.get_enacted_policies():
|
||||||
enacted_count[Policy(policy_str)] += 1
|
enacted_count[policy] += 1
|
||||||
message_content = [
|
message_content = [
|
||||||
f"{self.get_player_role().mention} A **{last_enacted.name}** policy {last_enacted.square_emoji()} has been enacted!",
|
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",
|
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.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])),
|
" ".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))
|
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
|
@game_started
|
||||||
@save_on_success
|
@save_on_success
|
||||||
async def kill_player(self, player: discord.Member):
|
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)
|
game = await self.get_running_game_or_error_message(ctx)
|
||||||
await self.check_is_administrator_or_gm(ctx)
|
await self.check_is_administrator_or_gm(ctx)
|
||||||
await self.check_in_admin_channel_or_error_message(ctx, game)
|
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()
|
policies = await game.draw_policies()
|
||||||
message_content = [
|
message_content = [
|
||||||
"The following policies have been drawn:",
|
"The following policies have been drawn:",
|
||||||
|
|
@ -303,6 +306,29 @@ class SecretBot(commands.Cog):
|
||||||
await game.kill_player(player)
|
await game.kill_player(player)
|
||||||
await ctx.reply(":dagger: The order has been executed.")
|
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__':
|
if __name__ == '__main__':
|
||||||
argparser = argparse.ArgumentParser(description = "Secret Hitler helper bot", formatter_class = argparse.ArgumentDefaultsHelpFormatter)
|
argparser = argparse.ArgumentParser(description = "Secret Hitler helper bot", formatter_class = argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue