Compare commits

..

5 Commits

4 changed files with 123 additions and 52 deletions

View File

@ -70,23 +70,43 @@ async def setup(ctx):
await send_friendly(ctx, "This channel can now play Werewolf")
def game_running(command):
def channel_setup(command):
@functools.wraps(command)
async def wrapper(ctx):
async def wrapper(ctx, *args, **kwargs):
if ctx.channel not in game_instances:
await send_wrong(ctx, f"No game setup yet. Use {PREFIX}game setup")
elif game_instances[ctx.channel].running:
else:
await command(ctx, *args, **kwargs)
return wrapper
def game_not_running(command):
@functools.wraps(command)
@channel_setup
async def wrapper(ctx, *args, **kwargs):
if game_instances[ctx.channel].running:
await send_wrong(ctx, "Sorry! A game is already running")
else:
await command(ctx)
await command(ctx, *args, **kwargs)
return wrapper
def game_running(command):
@functools.wraps(command)
@channel_setup
async def wrapper(ctx, *args, **kwargs):
if not game_instances[ctx.channel].running:
await send_wrong(ctx, "No game is running")
else:
await command(ctx, *args, **kwargs)
return wrapper
def error_handling(command):
@functools.wraps(command)
async def wrapper(ctx):
async def wrapper(ctx, *args, **kwargs):
try:
await command(ctx)
await command(ctx, *args, **kwargs)
except ValueError as error:
await send_wrong(ctx, str(error))
except asyncio.TimeoutError:
@ -95,31 +115,47 @@ def error_handling(command):
@game.command()
@game_running
@game_not_running
@error_handling
async def start(ctx):
await game_instances[ctx.channel].game()
game_instances[ctx.channel].game = bot.loop.create_task(game_instances[ctx.channel].round())
await game_instances[ctx.channel].game
@game.command()
@game_running
@channel_setup
async def stop(ctx):
game_instances[ctx.channel].game.cancel()
await send_friendly(ctx, "Game canceled")
@game.command()
@game_not_running
@error_handling
async def players(ctx):
await game_instances[ctx.channel].set_players(ctx.message)
@game.command()
@game_running
@game_not_running
@error_handling
async def roles(ctx):
await game_instances[ctx.channel].set_roles(ctx.message.content.split()[3:]) # exclude commands
async def roles(ctx, *args):
await game_instances[ctx.channel].set_roles(args)
@game.command()
@game_not_running
@error_handling
async def minutes(ctx, i):
await game_instances[ctx.channel].set_time(i)
# ONLY FOR TESTING
@game.command()
@game_running
@error_handling
async def vote(ctx):
await game_instances[ctx.channel].vote()
async def time(ctx):
await send_friendly(ctx, game_instances[ctx.channel].remaining_time_string())
# smaller commands
@ -140,10 +176,10 @@ async def ping(ctx):
def developer(command):
@functools.wraps(command)
async def wrapper(ctx):
async def wrapper(ctx, *args, **kwargs):
DEV_ID = 461892912821698562
if ctx.author.id == DEV_ID:
await command(ctx)
await command(ctx, *args, **kwargs)
else:
await send_wrong(ctx, "This command is not for you!")
return wrapper
@ -157,8 +193,9 @@ async def logout(ctx):
@bot.command()
@developer
async def debug(ctx):
async def debug(ctx, *args):
print("DEBUG")
print(ctx.message.author.id)
print(ctx.args)
print(ctx.kwargs)
bot.run(TOKEN)

View File

@ -1,4 +1,5 @@
from random import shuffle
import time
import asyncio
import discord
from werewolf_roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role
@ -13,6 +14,7 @@ class Game:
self.channel = channel
self.player_list = []
self.role_list = []
self.discussion_time = 300 # seconds
async def send(self, message):
await self.channel.send(embed=discord.Embed(description=message, color=0x00ffff))
@ -28,6 +30,10 @@ class Game:
self.role_list = [Role.match(r) for r in suggestions] # raises ValueError
await self.send(f"Roles: {', '.join(r.name() for r in self.role_list)}") # send confirmation
async def set_time(self, msg):
self.discussion_time = int(msg) * 60 + 1
await self.send(f"You have set the discussion time to {self.discussion_time//60} minutes") # send confirmation
def check(self):
if not 0 <= len(self.player_list) <= 10:
raise ValueError(f"Invalid number of players: {len(self.player_list)}")
@ -36,21 +42,25 @@ class Game:
def setup(self):
self.role = dict()
# setting default value
for r in Role.__subclasses__():
if r not in [Werewolf, Mason, No_role]:
r(self)
self.role[Werewolf] = []
self.role[Mason] = []
if r == No_role:
continue
if r in [Werewolf, Mason]:
self.role[r] = []
else:
r(self).add_yourself()
self.voting_list = self.player_list + [No_player()]
for c in self.voting_list:
c.tally = 0
c.won = c.dead = False
c.reset()
def distribute_roles(self):
shuffle(self.role_list)
for i in range(len(self.player_list)):
self.role_list[i](self, self.player_list[i])
self.middle_card = self.role_list[-3:]
role_obj = self.role_list[i](self, self.player_list[i])
self.player_list[i].setRole(role_obj)
role_obj.add_yourself()
self.middle_card = [r(self) for r in self.role_list[-3:]]
async def start_night(self):
await self.for_all_player(lambda p: p.send_normal("*The night has begun*"))
@ -74,12 +84,32 @@ class Game:
async def start_day(self):
await self.send("The day has started")
def remaining_time_string(self):
t = int(self.start_time + self.discussion_time - time.time())
return f"{t//60} minute(s) and {t%60} second(s)"
async def discussion_timer(self):
self.start_time = time.time()
await self.send(f"You have {self.remaining_time_string()} to discuss")
await asyncio.sleep(self.discussion_time / 2)
await self.send(f"{self.remaining_time_string()} remaining")
await asyncio.sleep(self.discussion_time / 2 - 60)
await self.send(f"{self.remaining_time_string()} remaining")
await asyncio.sleep(30)
await self.send(f"{self.remaining_time_string()} remaining")
await asyncio.sleep(30)
async def early_vote(self):
await self.for_all_player(lambda p: p.ready_to_vote())
async def discussion_finished(self):
done, pending = await asyncio.wait([self.discussion_timer(), self.early_vote()], return_when=asyncio.FIRST_COMPLETED)
for p in pending:
p.cancel()
await asyncio.wait(pending)
async def vote(self):
# vote
# replace with dm: await self.receive('$vote')
await self.send("Vote in DM")
await self.for_all_player(lambda p: p.cast_vote("Who do you want to kill?", self.voting_list))
def tally(self):
@ -145,15 +175,17 @@ class Game:
return werewolf_won, village_won, tanner_won, dead
async def result(self, werewolf_won, village_won, tanner_won, dead):
winnning = []
if werewolf_won:
winnning = ["Werewolves won!"]
winnning.append("Werewolves")
if village_won:
winnning = ["Village won!"]
winnning.append("Village")
if tanner_won:
winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner won")
winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner")
if len(winnning) == 0:
winnning = ["No one"]
embed = discord.Embed(title=' and '.join(winnning), color=0x00ffff)
embed = discord.Embed(title=f"{' and '.join(winnning)} won!", color=0x00ffff)
for p in self.player_list:
won_emoji = ":trophy:" if p.won else ":frowning2:"
dead_emoji = ":skull:" if p.dead else ":no_mouth:"
@ -165,24 +197,20 @@ class Game:
def end(self):
self.running = False
async def game(self):
async def round(self):
try:
self.check()
self.running = True
self.setup()
self.running = True
self.distribute_roles()
await self.start_night()
await self.send_role()
await self.night_phases()
await self.start_day()
# discussion timer
await self.discussion_finished()
await self.vote()
self.tally()
await self.result(*self.who_won(self.who_dead()))
await self.send("Round ended")
finally:
self.end()

View File

@ -14,6 +14,10 @@ class Player:
def setRole(self, role):
self.day_role = self.night_role = role
def reset(self):
self.tally = 0
self.won = self.dead = False
def name(self):
return self.member.name
@ -81,6 +85,12 @@ class Player:
async def cast_vote(self, question, options):
self.vote = options[await self.get_choice(question, options)]
async def ready_to_vote(self):
def check(msg):
return msg.channel == self.dm and msg.author == self.member and msg.content.casefold() == "vote"
await self.game.bot.wait_for('message', check=check)
await self.send_confirmation("You are ready to vote")
class No_player(Player):
def name(self):

View File

@ -1,4 +1,5 @@
import functools
from fuzzywuzzy import fuzz
from werewolf_players import No_player
@ -6,8 +7,6 @@ class Role:
def __init__(self, game, player=No_player()):
self.game = game
self.player = player
self.player.setRole(self)
self.add_yourself()
def add_yourself(self):
self.game.role[type(self)] = self
@ -20,10 +19,7 @@ class Role:
@staticmethod
def match(message):
for role_class in Role.__subclasses__():
if message.casefold() == role_class.name():
return role_class
raise ValueError(f"Invalid role: {message}")
return max(Role.__subclasses__(), key=lambda role_class: fuzz.ratio(message, role_class.name()))
@staticmethod
def no_player(func):
@ -129,7 +125,7 @@ class Robber(Role):
self.choice = await self.player.get_choice("Which player do you want to rob?", self.player.other())
@Role.no_player
def simulate(self):
async def simulate(self):
self.player.swap(self.player.other()[self.choice])
@Role.no_player
@ -143,7 +139,7 @@ class Troublemaker(Role):
self.A, self.B = await self.player.get_double_choice("Who do you want to exchange? (send two numbers)", self.player.other())
@Role.no_player
def simulate(self):
async def simulate(self):
self.player.other()[self.A].swap(self.player.other()[self.B])
@ -153,14 +149,14 @@ class Drunk(Role):
self.choice = await self.player.get_choice("Which card from the middle do you want to take?", ["left", "middle", "right"])
@Role.no_player
def simulate(self):
async def simulate(self):
self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role # swap
class Insomniac(Role):
@Role.no_player
async def send_info(self):
await self.player.send_info(f"You are now: {self.player.day_role}")
await self.player.send_info(f"You are: {self.player.day_role}")
class Villager(Role):