Seer and voting fixes

pull/5/head
Bobloy 7 years ago
parent bae1355037
commit d0facafb92

@ -1,5 +1,9 @@
from typing import List
import discord import discord
# Import all roles here # Import all roles here
from werewolf.role import Role
from werewolf.roles.seer import Seer from werewolf.roles.seer import Seer
from werewolf.roles.vanillawerewolf import VanillaWerewolf from werewolf.roles.vanillawerewolf import VanillaWerewolf
from werewolf.roles.villager import Villager from werewolf.roles.villager import Villager
@ -23,18 +27,18 @@ double digit position preempted by `-`
""" """
async def parse_code(code): async def parse_code(code, game):
"""Do the magic described above""" """Do the magic described above"""
out = [] out: List[Role] = []
decode = code.copy() # for now, pass exact names decode = code.copy() # for now, pass exact names
for role_id in decode: for role_id in decode:
print(role_id) print(role_id)
if role_id == "Villager": if role_id == "Villager":
role = Villager role = Villager(game)
elif role_id == "VanillaWerewolf": elif role_id == "VanillaWerewolf":
role = VanillaWerewolf role = VanillaWerewolf(game)
elif role_id == "Seer": elif role_id == "Seer":
role = Seer role = Seer(game)
else: # Fail to parse else: # Fail to parse
return None return None
out.append(role) out.append(role)

@ -1,16 +1,21 @@
import asyncio import asyncio
import random import random
from typing import List
import discord import discord
from werewolf.builder import parse_code from werewolf.builder import parse_code
from werewolf.player import Player from werewolf.player import Player
from werewolf.role import Role
class Game: class Game:
""" """
Base class to run a single game of Werewolf Base class to run a single game of Werewolf
""" """
players: List[Player]
roles: List[Role]
channel_category: discord.CategoryChannel
village_channel: discord.TextChannel village_channel: discord.TextChannel
default_secret_channel = { default_secret_channel = {
@ -26,18 +31,12 @@ class Game:
day_vote_count = 3 day_vote_count = 3
# def __new__(cls, guild, game_code): def __init__(self, guild: discord.Guild, role: discord.Role, game_code=None):
# game_code = ["VanillaWerewolf", "Villager", "Villager"]
#
# return super().__new__(cls, guild, game_code)
def __init__(self, guild: discord.Guild, role: discord.Role, game_code):
self.guild = guild self.guild = guild
self.game_code = ["VanillaWerewolf"] self.game_code = ["Seer", "VanillaWerewolf", "Villager"]
self.game_role = role self.game_role = role
self.roles = [] self.roles = []
self.players = [] self.players = []
self.day_vote = {} # author: target self.day_vote = {} # author: target
@ -50,6 +49,7 @@ class Game:
self.day_time = False self.day_time = False
self.day_count = 0 self.day_count = 0
self.ongoing_vote = False
self.channel_category = None self.channel_category = None
self.village_channel = None self.village_channel = None
@ -99,6 +99,7 @@ class Game:
self.roles = [] self.roles = []
return False return False
self.started = True
await self.assign_roles() await self.assign_roles()
# Create category and channel with individual overwrites # Create category and channel with individual overwrites
@ -109,15 +110,16 @@ class Game:
self.game_role: discord.PermissionOverwrite(read_messages=True, send_messages=True) self.game_role: discord.PermissionOverwrite(read_messages=True, send_messages=True)
} }
self.channel_category = await self.guild.create_category("ww-game", overwrites=overwrite, reason="New game of " self.channel_category = await self.guild.create_category("ww-game",
"werewolf") overwrites=overwrite,
reason="(BOT) New game of werewolf")
# for player in self.players: # for player in self.players:
# overwrite[player.member] = discord.PermissionOverwrite(read_messages=True) # overwrite[player.member] = discord.PermissionOverwrite(read_messages=True)
self.village_channel = await self.guild.create_text_channel("Village Square", self.village_channel = await self.guild.create_text_channel("Village Square",
overwrites=overwrite, overwrites=overwrite,
reason="New game of werewolf", reason="(BOT) New game of werewolf",
category=self.channel_category) category=self.channel_category)
# Assuming everything worked so far # Assuming everything worked so far
@ -136,7 +138,7 @@ class Game:
channel = await self.guild.create_text_channel(channel_id, channel = await self.guild.create_text_channel(channel_id,
overwrites=overwrite, overwrites=overwrite,
reason="Ww game secret channel", reason="(BOT) WW game secret channel",
category=self.channel_category) category=self.channel_category)
self.p_channels[channel_id]["channel"] = channel self.p_channels[channel_id]["channel"] = channel
@ -208,13 +210,15 @@ class Game:
return return
self.can_vote = True self.can_vote = True
await asyncio.sleep(12) # 4 minute days FixMe to 120 later await asyncio.sleep(24) # 4 minute days FixMe to 120 later
if check(): if check():
return return
await self.village_channel.send(embed=discord.Embed(title="**Two minutes of daylight remain...**")) await self.village_channel.send(embed=discord.Embed(title="**Two minutes of daylight remain...**"))
await asyncio.sleep(12) # 4 minute days FixMe to 120 later await asyncio.sleep(24) # 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 (can_vote?)
while self.ongoing_vote:
asyncio.sleep(5)
if check(): if check():
return return
@ -227,16 +231,17 @@ class Game:
data = {"player": target} data = {"player": target}
await self._notify(2, data) await self._notify(2, data)
self.ongoing_vote = True
self.used_votes += 1 self.used_votes += 1
self.can_vote = False await self.speech_perms(self.village_channel, target.member) # Only target can talk
await self.speech_perms(self.village_channel, target.member)
await self.village_channel.send( await self.village_channel.send(
"**{} will be put to trial and has 30 seconds to defend themselves**".format(target.mention)) "**{} will be put to trial and has 30 seconds to defend themselves**".format(target.mention))
await asyncio.sleep(30) await asyncio.sleep(30)
await self.speech_perms(self.village_channel, target.member, undo=True) await self.speech_perms(self.village_channel, target.member, undo=True) # No one can talk
message = await self.village_channel.send( message = await self.village_channel.send(
"Everyone will now vote whether to lynch {}\n" "Everyone will now vote whether to lynch {}\n"
@ -244,42 +249,46 @@ class Game:
"*Majority rules, no-lynch on ties, " "*Majority rules, no-lynch on ties, "
"vote both or neither to abstain, 15 seconds to vote*".format(target.mention)) "vote both or neither to abstain, 15 seconds to vote*".format(target.mention))
await self.village_channel.add_reaction("👍") await message.add_reaction("👍")
await self.village_channel.add_reaction("👎") await message.add_reaction("👎")
await asyncio.sleep(15) await asyncio.sleep(15)
reaction_list = message.reactions reaction_list = message.reactions
up_votes = sum(p.emoji == "👍" and not p.me for p in reaction_list) up_votes = sum(p for p in reaction_list if p.emoji == "👍" and not p.me)
down_votes = sum(p.emoji == "👎" and not p.me for p in reaction_list) down_votes = sum(p for p in reaction_list if p.emoji == "👎" and not p.me )
if len(down_votes) > len(up_votes): if down_votes > up_votes:
embed = discord.Embed(title="Vote Results", color=0xff0000) embed = discord.Embed(title="Vote Results", color=0xff0000)
else: else:
embed = discord.Embed(title="Vote Results", color=0x80ff80) embed = discord.Embed(title="Vote Results", color=0x80ff80)
embed.add_field(name="👎", value="**{}**".format(len(up_votes)), inline=True) embed.add_field(name="👎", value="**{}**".format(up_votes), inline=True)
embed.add_field(name="👍", value="**{}**".format(len(down_votes)), inline=True) embed.add_field(name="👍", value="**{}**".format(down_votes), inline=True)
await self.village_channel.send(embed=embed) await self.village_channel.send(embed=embed)
if len(down_votes) > len(up_votes): if down_votes > up_votes:
await self.village_channel.send("**Voted to lynch {}!**".format(target.mention)) await self.village_channel.send("**Voted to lynch {}!**".format(target.mention))
await self.lynch(target) await self.lynch(target)
self.can_vote = False
else: else:
await self.village_channel.send("**{} has been spared!**".format(target.mention)) await self.village_channel.send("**{} has been spared!**".format(target.mention))
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
else: else:
await self.village_channel.send( await self.village_channel.send(
"**{}**/**{}** of today's votes have been used!\n" "**{}**/**{}** of today's votes have been used!\n"
"Nominate carefully..".format(self.used_votes, self.day_vote_count)) "Nominate carefully..".format(self.used_votes, self.day_vote_count))
self.can_vote = True # Only re-enable voting if more votes remain
self.ongoing_vote = False
if not self.can_vote: if not self.can_vote:
await self._at_day_end() await self._at_day_end()
else:
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:
@ -330,7 +339,7 @@ class Game:
return return
await self._notify(7) await self._notify(7)
await asyncio.sleep(15) await asyncio.sleep(10)
await self._at_day_start() await self._at_day_start()
async def _at_visit(self, target, source): # ID 8 async def _at_visit(self, target, source): # ID 8
@ -356,20 +365,22 @@ class Game:
############END Notify structure############ ############END Notify structure############
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 in range(len(self.players)):
player = self.players[i] player = self.players[i]
if player.alive: if player.alive:
status = "" status = ""
else: else:
status = "*Dead*" status = "*[Dead]*-"
if with_roles: if with_roles or not player.alive:
embed.add_field(name="ID# **{}**".format(i), embed.add_field(name="ID# **{}**".format(i),
value="{} {} {}".format(status, player.member.display_name, str(player.role)), inline=True) value="{}{}-{}".format(status, player.member.display_name, str(player.role)),
inline=True)
else: else:
embed.add_field(name="ID# **{}**".format(i), embed.add_field(name="ID# **{}**".format(i),
value="{} {}".format(status, player.member.display_name), inline=True) value="{}{}".format(status, player.member.display_name),
inline=True)
return await channel.send(embed=embed) return await channel.send(embed=embed)
@ -405,6 +416,8 @@ class Game:
self.players.append(Player(member)) self.players.append(Player(member))
await member.add_roles(*[self.game_role])
await channel.send("{} has been added to the game, " await channel.send("{} has been added to the game, "
"total players is **{}**".format(member.mention, len(self.players))) "total players is **{}**".format(member.mention, len(self.players)))
@ -422,6 +435,7 @@ class Game:
await channel.send("{} has left the game".format(member.mention)) await channel.send("{} has left the game".format(member.mention))
else: else:
self.players = [player for player in self.players if player.member != member] self.players = [player for player in self.players if player.member != member]
await member.remove_roles(*[self.game_role])
await channel.send("{} chickened out, player count is now **{}**".format(member.mention, len(self.players))) await channel.send("{} chickened out, player count is now **{}**".format(member.mention, len(self.players)))
async def choose(self, ctx, data): async def choose(self, ctx, data):
@ -436,7 +450,7 @@ class Game:
return return
if not player.alive: if not player.alive:
await ctx.send("**Corpses** can't vote...") await ctx.send("**Corpses** can't participate...")
return return
if player.role.blocked: if player.role.blocked:
@ -446,7 +460,7 @@ class Game:
# Let role do target validation, might be alternate targets # Let role do target validation, might be alternate targets
# I.E. Go on alert? y/n # I.E. Go on alert? y/n
await player.choose(ctx, data) await player.role.choose(ctx, data)
async def _visit(self, target, source): async def _visit(self, target, source):
await target.role.visit(source) await target.role.visit(source)
@ -476,7 +490,7 @@ class Game:
return return
if not player.alive: if not player.alive:
await channel.send("Corpses can't vote") await channel.send("Corpses can't vote...")
return return
if channel == self.village_channel: if channel == self.village_channel:
@ -536,7 +550,9 @@ class Game:
out = "**{ID}** - " + method out = "**{ID}** - " + method
return out.format(ID=target.id, target=target.member.display_name) return out.format(ID=target.id, target=target.member.display_name)
else: else:
return "**{ID}** - {target} was found dead".format(ID=target.id, target=target.member.display_name) return "**{ID}** - {target} the {role} was found dead".format(ID=target.id,
target=target.member.display_name,
role=await target.role.get_role())
async def _quit(self, player): async def _quit(self, player):
""" """
@ -607,7 +623,7 @@ class Game:
if self.game_code is None: if self.game_code is None:
return False return False
self.roles = await parse_code(self.game_code) self.roles = await parse_code(self.game_code, self)
if not self.roles: if not self.roles:
return False return False
@ -618,11 +634,10 @@ class Game:
self.players.sort(key=lambda pl: pl.member.display_name.lower()) self.players.sort(key=lambda pl: pl.member.display_name.lower())
if len(self.roles) != len(self.players): if len(self.roles) != len(self.players):
await self.village_channel("Unhandled error - roles!=players") await self.village_channel.send("Unhandled error - roles!=players")
return False return False
for idx, role in enumerate(self.roles): for idx, role in enumerate(self.roles):
self.roles[idx] = role(self)
await self.roles[idx].assign_player(self.players[idx]) await self.roles[idx].assign_player(self.players[idx])
# Sorted players, now assign id's # Sorted players, now assign id's
await self.players[idx].assign_id(idx) await self.players[idx].assign_id(idx)
@ -650,18 +665,30 @@ class Game:
await channel.set_permissions(self.game_role, read_messages=True, send_messages=False) await channel.set_permissions(self.game_role, read_messages=True, send_messages=False)
await channel.set_permissions(member, send_messages=True) await channel.set_permissions(member, send_messages=True)
async def normal_perms(self, channel, member_list): async def normal_perms(self, channel):
await channel.set_permissions(self.game_role, read_messages=True, send_messages=True) await channel.set_permissions(self.game_role, read_messages=True, send_messages=True)
# for member in member_list:
# await channel.set_permissions(member, read_messages=True)
async def _check_game_over(self): async def _check_game_over(self):
# return # ToDo: re-enable game-over checking
alive_players = [player for player in self.players if player.alive] alive_players = [player for player in self.players if player.alive]
if len(alive_players) <= 2: if len(alive_players) <= 0:
await self.village_channel.send(embed=discord.Embed(title="**Everyone is dead! Game Over!**"))
self.game_over = True
elif len(alive_players) == 1:
self.game_over = True self.game_over = True
await self._announce_winners(alive_players)
elif len(alive_players) == 2:
# Check 1v1 victory conditions ToDo # Check 1v1 victory conditions ToDo
pass self.game_over = True
alignment1 = alive_players[0].role.alignment
alignment2 = alive_players[1].role.alignment
if alignment1 == alignment2: # Same team
winners = alive_players
else:
winners = [max(alive_players, key=lambda p: p.role.alignment)]
await self._announce_winners(winners)
else: else:
# Check if everyone is on the same team # Check if everyone is on the same team
alignment = alive_players[0].role.alignment # Get first allignment and compare to rest alignment = alive_players[0].role.alignment # Get first allignment and compare to rest
@ -686,8 +713,9 @@ class Game:
await self.generate_targets(self.village_channel, True) await self.generate_targets(self.village_channel, True)
async def _end_game(self): async def _end_game(self):
# Remove game_role access for potential archiving for now # Remove game_role access for potential archiving for now
await self.village_channel.set_permissions(self.game_role, overwrite=None) reason = '(BOT) End of WW game'
pass await self.village_channel.set_permissions(self.game_role, overwrite=None, reason=reason)
await self.channel_category.set_permissions(self.game_role, overwrite=None, reason=reason)
await self.channel_category.edit(reason=reason, name="Archived Game")

@ -29,7 +29,8 @@ class Seer(Role):
(self._at_hang, 0), (self._at_hang, 0),
(self._at_day_end, 0), (self._at_day_end, 0),
(self._at_night_start, 2), (self._at_night_start, 2),
(self._at_night_end, 4) (self._at_night_end, 4),
(self._at_visit, 0)
] ]
# async def on_event(self, event, data): # async def on_event(self, event, data):
@ -96,15 +97,22 @@ class Seer(Role):
# pass # pass
async def _at_night_start(self, data=None): async def _at_night_start(self, data=None):
if not self.player.alive:
return
self.see_target = None
await self.game.generate_targets(self.player.member) await self.game.generate_targets(self.player.member)
await self.player.send_dm("{}\n**Pick a target to see tonight**\n") await self.player.send_dm("**Pick a target to see tonight**\n")
async def _at_night_end(self, data=None): async def _at_night_end(self, data=None):
target = await self.game.visit(self.see_target) if self.see_target is None:
if self.player.alive:
await self.player.send_dm("You will not use your powers tonight...")
return
target = await self.game.visit(self.see_target, self.player)
alignment = None alignment = None
if target: if target:
alignment = await target.see_alignment(self.player) alignment = await target.role.see_alignment(self.player)
if alignment == "Werewolf": if alignment == "Werewolf":
out = "Your insight reveals this player to be a **Werewolf!**" out = "Your insight reveals this player to be a **Werewolf!**"
@ -133,6 +141,10 @@ class Seer(Role):
async def choose(self, ctx, data): async def choose(self, ctx, data):
"""Handle night actions""" """Handle night actions"""
if not self.player.alive: # FixMe: Game handles this?
await self.player.send_dm("You're already dead!")
return
target_id = int(data) target_id = int(data)
try: try:
target = self.game.players[target_id] target = self.game.players[target_id]

@ -4,6 +4,7 @@ import discord
from discord.ext import commands from discord.ext import commands
from redbot.core import Config from redbot.core import Config
from redbot.core import RedContext from redbot.core import RedContext
from redbot.core.bot import Red
from werewolf.game import Game from werewolf.game import Game
@ -14,12 +15,12 @@ class Werewolf:
""" """
games: Dict[int, Game] games: Dict[int, Game]
def __init__(self, bot): def __init__(self, bot: Red):
self.bot = bot self.bot = bot
self.config = Config.get_conf(self, identifier=87101114101119111108102, force_registration=True) self.config = Config.get_conf(self, identifier=87101114101119111108102, force_registration=True)
default_global = {} default_global = {}
default_guild = { default_guild = {
"role": None "role_id": None
} }
self.config.register_global(**default_global) self.config.register_global(**default_global)
@ -47,7 +48,7 @@ class Werewolf:
Assign the game role Assign the game role
This role should not be manually assigned This role should not be manually assigned
""" """
await self.config.guild(ctx.guild).role.set(role.id) await self.config.guild(ctx.guild).role_id.set(role.id)
await ctx.send("Game role has been set to **{}**".format(role.name)) await ctx.send("Game role has been set to **{}**".format(role.name))
@commands.group() @commands.group()
@ -64,13 +65,11 @@ class Werewolf:
""" """
Create and join a new game of Werewolf Create and join a new game of Werewolf
""" """
game = await self._get_game(ctx, game_code) game = await self._get_game(ctx, game_code)
if not game: if not game:
await ctx.send("Failed to start a new game") await ctx.send("Failed to start a new game")
else: else:
await ctx.send("Game is ready to join! Use `[p]`ww join`") await ctx.send("Game is ready to join! Use `[p]ww join`")
@commands.guild_only() @commands.guild_only()
@ww.command() @ww.command()
@ -79,7 +78,7 @@ class Werewolf:
Joins a game of Werewolf Joins a game of Werewolf
""" """
game = await self._get_game(ctx.guild) game = await self._get_game(ctx)
if not game: if not game:
await ctx.send("No game to join!\nCreate a new one with `[p]ww new`") await ctx.send("No game to join!\nCreate a new one with `[p]ww new`")
@ -94,7 +93,7 @@ class Werewolf:
Quit a game of Werewolf Quit a game of Werewolf
""" """
game = await self._get_game(ctx.guild) game = await self._get_game(ctx)
await game.quit(ctx.author, ctx.channel) await game.quit(ctx.author, ctx.channel)
@ -104,7 +103,7 @@ class Werewolf:
""" """
Checks number of players and attempts to start the game Checks number of players and attempts to start the game
""" """
game = await self._get_game(ctx.guild) game = await self._get_game(ctx)
if not game: if not game:
await ctx.send("No game running, cannot start") await ctx.send("No game running, cannot start")
@ -116,7 +115,7 @@ class Werewolf:
""" """
Stops the current game Stops the current game
""" """
game = await self._get_game(ctx.guild) game = await self._get_game(ctx)
if not game: if not game:
await ctx.send("No game running, cannot stop") await ctx.send("No game running, cannot stop")
@ -130,7 +129,7 @@ class Werewolf:
""" """
try: try:
target_id = int(target_id) target_id = int(target_id)
except: except ValueError:
target_id = None target_id = None
if target_id is None: if target_id is None:
@ -148,7 +147,7 @@ class Werewolf:
# return # return
# else: # else:
game = await self._get_game(ctx.guild) game = await self._get_game(ctx)
if game is None: if game is None:
await ctx.send("No game running, cannot vote") await ctx.send("No game running, cannot vote")
@ -186,17 +185,17 @@ class Werewolf:
await game.choose(ctx, data) await game.choose(ctx, data)
async def _get_game(self, ctx, game_code=None): async def _get_game(self, ctx: RedContext, game_code=None):
if ctx.guild is None: if ctx.guild is None:
# Private message, can't get guild # Private message, can't get guild
ctx.send("Cannot start game from PM!")
return None return None
if ctx.guild.id not in self.games or self.games[ctx.guild.id].game_over: if ctx.guild.id not in self.games or self.games[ctx.guild.id].game_over:
await ctx.send("Starting a new game...") await ctx.send("Starting a new game...")
if not game_code: role_id = await self.config.guild(ctx.guild).role_id()
return None role = discord.utils.get(ctx.guild.roles, id=role_id)
role = await self.config.guild(ctx.guild).role()
role = discord.utils.get(ctx.guild.roles, id=role)
if role is None: if role is None:
ctx.send("Game role is invalid, cannot start new game")
return None return None
self.games[ctx.guild.id] = Game(ctx.guild, role, game_code) self.games[ctx.guild.id] = Game(ctx.guild, role, game_code)

Loading…
Cancel
Save