diff --git a/src/interaction/discord_wrapper.py b/src/interaction/discord_wrapper.py deleted file mode 100644 index 3f21e9f..0000000 --- a/src/interaction/discord_wrapper.py +++ /dev/null @@ -1,57 +0,0 @@ -"""This module is a wrapper for messaging functionality Discord to play text based multi-player games""" - -# discord imports -import discord -from discord.ext import commands - - -class Client: - def create_task(): - pass - - -class User: - def name() -> str: - pass - - def DM() -> DM: - pass - - -class Message: - def mentions() -> User: - pass - - -class Communication: - def send(): - pass - - def send_embed(): - pass - - def command(): - pass - - def wait_for_message() -> Message: - pass - - -class DM(Communication): - pass - - -class Room(Communication): - pass - - -class Context: - pass - - -def choices(): - pass - - -def choice(): - pass diff --git a/src/interaction/template.py b/src/interaction/template.py deleted file mode 100644 index 5cdcdea..0000000 --- a/src/interaction/template.py +++ /dev/null @@ -1,57 +0,0 @@ -"""This module is a wrapper template for messaging functionality to play text based multi-player games""" - - -class Interaction: - def query(communication, question: str, options: list, exp_ans=1): - pass - - -class Client: - def create_task(func): - pass - - -class User: - def name(): - pass - - def DM(): - pass - - -class Communication: # better name - def send(str): - pass - - def send_embed(embed): - pass - - def command(): - pass - - def wait_for_message(): - pass - - -class DM(Communication): - pass - - -class Room(Communication): - pass - - -class Context: - def User(): - pass - - def Communication(): - pass - - def Message(): - pass - - def mentions(): - pass - - # args? \ No newline at end of file diff --git a/src/package/developer.py b/src/package/developer.py index 920a6df..c54a73a 100644 --- a/src/package/developer.py +++ b/src/package/developer.py @@ -41,6 +41,15 @@ class Developer(commands.Cog): @commands.command() @commands.check(is_dev) async def debug(self, ctx, *args): + embed = discord.Embed(title=f"Village won!", color=0x00ffff) + won_emoji = ":trophy:" + dead_emoji = ":test:" + tab = "\t" + space = "<:space:705863033871663185>" + embed.add_field(name=str("Name"), value=f"{won_emoji}{space}{dead_emoji}{space}{space}{3}:ballot_box:{tab}role: werewolf{tab}(was: drunk){tab}:point_right: someone", inline=False) + await ctx.send(embed=embed) + await ctx.send(":test::skull:") + for emoji in ctx.guild.emojis: await ctx.send(emoji) print(emoji.id) diff --git a/src/package/games/game.py b/src/package/games/game.py index ac8cf93..7a4ccf6 100644 --- a/src/package/games/game.py +++ b/src/package/games/game.py @@ -1,49 +1,20 @@ -"""Has a single class: Game""" - -# standard library imports from abc import ABC -import asyncio - -# discord imports -import discord - -# local imports -from .players import Player class Game(ABC): - """This (abstract) class is a template for main-game-loop objects for games""" @classmethod def name(cls): return "Game" - player_cls = Player - def __init__(self, bot, channel): self.bot = bot self.channel = channel self.running = False self.player_list = [] - async def send(self, message): - await self.channel.send(embed=discord.Embed(description=message, color=0x00ffff)) - - async def for_all_player(self, call): - await asyncio.gather(*[call(p) for p in self.player_list]) - - async def set_players(self, msg): - self.player_list = [await self.player_cls.make(mem, self) for mem in msg.mentions] - await self.send(f"Players: {', '.join(p.name() for p in self.player_list)}") # send confirmation - - def check(self): - pass - - def setup(self): - pass - - def end(self): - self.running = False - async def round(self): pass + + async def set_players(self): + pass diff --git a/src/package/games/game_cog.py b/src/package/games/game_cog.py index 28a5886..7c9083e 100644 --- a/src/package/games/game_cog.py +++ b/src/package/games/game_cog.py @@ -1,11 +1,10 @@ """Has a single class: Game_cog""" # standard library imports -from typing import Dict +from typing import Dict, Type # discord imports import discord -from discord.ext import commands # local imports from ..send_message import Send_message @@ -15,11 +14,10 @@ from .game import Game class Game_cog(Send_message): """This (abstract) class is are common function for the Game Cog's (setup-game, pre-game, in-game), mainly has checker functions""" - game_cls = Game - - def __init__(self, bot): + def __init__(self, bot, game_cls: Type[Game]): self.bot = bot - self.game_instances = {} # TODO: type hint? Dict[discord.TextChannel, self.game_cls] + self.game_cls = game_cls + self.game_instances = Dict[discord.TextChannel, self.game_cls] async def setup_check(self, ctx): if ctx.channel not in self.game_instances: @@ -46,7 +44,7 @@ class Game_cog(Send_message): async def reset(self, ctx): """This function deletes the game instance for this channel""" - if await self.setup_check(ctx): + if self.setup_check(ctx): del self.game_instances[ctx.channel] # TODO: better info message @@ -62,11 +60,8 @@ class Game_cog(Send_message): embed.set_footer(text="Have fun!") await ctx.send(embed=embed) - # TODO: can't one access self instead of ctx.cog? - def pre_game(): - async def predicate(ctx): - return await ctx.cog.setup_check(ctx) and await ctx.cog.not_running_check(ctx) - return commands.check(predicate) + async def pre_game_check(self, ctx): + return self.setup_check(ctx) and self.not_running_check(ctx) async def players(self, ctx): await self.game_instances[ctx.channel].set_players(ctx.message) @@ -75,10 +70,8 @@ class Game_cog(Send_message): self.game_instances[ctx.channel].game = self.bot.loop.create_task(self.game_instances[ctx.channel].round()) await self.game_instances[ctx.channel].game - def in_game(): - async def predicate(ctx): - return await ctx.cog.setup_check(ctx) and await ctx.cog.running_check(ctx) - return commands.check(predicate) + async def in_game_check(self, ctx): + return self.setup_check(ctx) and self.running_check(ctx) async def stop(self, ctx): self.game_instances[ctx.channel].game.cancel() diff --git a/src/package/games/werewolf/cog.py b/src/package/games/werewolf/cog.py index 210d7b5..d210fd1 100644 --- a/src/package/games/werewolf/cog.py +++ b/src/package/games/werewolf/cog.py @@ -11,8 +11,6 @@ from .game import Werewolf_game class Werewolf_cog(Game_cog, commands.Cog): """This singleton class is a Discord Cog for the interaction in the werewolf game""" - game_cls = Werewolf_game - @commands.group(invoke_without_command=True) async def werewolf(self, ctx): # TODO: isn't there a better way to have a default subcommand? Maybe invoke super().info()? @@ -21,12 +19,12 @@ class Werewolf_cog(Game_cog, commands.Cog): @werewolf.command() async def info(self, ctx): """Send information about the subcommands for the game""" - await super().info(ctx) + await super().info(ctx, Werewolf_game) @werewolf.command() async def setup(self, ctx): """This function creates an game instance for this channel""" - await super().setup(ctx) + await super().setup(ctx, Werewolf_game) @werewolf.command() async def reset(self, ctx): @@ -34,42 +32,41 @@ class Werewolf_cog(Game_cog, commands.Cog): await super().reset(ctx) @werewolf.command() - @Game_cog.pre_game() + @commands.check(Game_cog.pre_game_check) async def players(self, ctx): """registers all mentioned players for the game""" await super().players(ctx) @werewolf.command() - @Game_cog.pre_game() + @commands.check(Game_cog.pre_game_check) async def roles(self, ctx, *args): """registers roles you want to play with""" await self.game_instances[ctx.channel].set_roles(args) @werewolf.command() - @Game_cog.pre_game() - async def minutes(self, ctx, i: int): + @commands.check(Game_cog.pre_game_check) + async def minutes(self, ctx, i): """set discussion time""" await self.game_instances[ctx.channel].set_time(i) @werewolf.command() - @Game_cog.pre_game() + @commands.check(Game_cog.pre_game_check) async def start(self, ctx): - print(await self.setup_check(ctx), await self.not_running_check(ctx)) """starts a round of werewolf""" await super().start(ctx) @werewolf.command() - @Game_cog.in_game() + @commands.check(Game_cog.in_game_check) async def stop(self, ctx): """aborts the current round of werewolf""" await super().stop(ctx) @werewolf.command() - @Game_cog.in_game() + @commands.check(Game_cog.in_game_check) async def time(self, ctx): """checks how much discussion time there is left""" await self.send_friendly(ctx, self.game_instances[ctx.channel].remaining_time_string()) def setup(bot): - bot.add_cog(Werewolf_cog(bot)) + bot.add_cog(Werewolf_cog(bot, Werewolf_game)) diff --git a/src/package/games/werewolf/game.py b/src/package/games/werewolf/game.py index dee4564..34fe62d 100644 --- a/src/package/games/werewolf/game.py +++ b/src/package/games/werewolf/game.py @@ -1,54 +1,53 @@ -"""Has a single class: Werewolf_game""" - -# standard library imports from random import shuffle import time import asyncio - -# discord imports import discord - -# local imports from .roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role from .players import Werewolf_player, No_player -from ..game import Game -class Werewolf_game(Game): - """This class simulates One Night Ultimate Werewolf with the help of Werewolf_player and Werewolf_role""" +class Werewolf_game: @classmethod def name(cls): return "One Night Ultimate Werewolf" - player_cls = Werewolf_player - def __init__(self, bot, channel): - super().__init__(bot, channel) + self.running = False + self.bot = bot + self.channel = channel + self.player_list = [] self.role_list = [] self.discussion_time = 301 # seconds + async def send(self, message): + await self.channel.send(embed=discord.Embed(description=message, color=0x00ffff)) + + async def for_all_player(self, call): + await asyncio.gather(*[call(p) for p in self.player_list]) + + async def set_players(self, msg): + self.player_list = [await Werewolf_player.make(mem, self) for mem in msg.mentions] + await self.send(f"Players: {', '.join(p.name() for p in self.player_list)}") # send confirmation + async def set_roles(self, suggestions): 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, minutes: int): - self.discussion_time = minutes * 60 + 1 + 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): - # TODO: min. player - if not 1 <= len(self.player_list) <= 10: + if not 0 <= len(self.player_list) <= 10: raise ValueError(f"Invalid number of players: {len(self.player_list)}") if not len(self.role_list) == (len(self.player_list) + 3): raise ValueError(f"Invalid number of roles: {len(self.role_list)} with {len(self.player_list)} players") def setup(self): - # TODO: better... self.role = dict() # setting default value for r in Role.__subclasses__(): - # TODO: every role should be a list (not only werewolves, masons, ...) if r == No_role: continue if r in [Werewolf, Mason]: @@ -61,15 +60,14 @@ class Werewolf_game(Game): def distribute_roles(self): shuffle(self.role_list) - # TODO: use zip for i in range(len(self.player_list)): role_obj = self.role_list[i](self, self.player_list[i]) - self.player_list[i].set_role(role_obj) + 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(f"*The night has begun* {':full_moon:'*10}")) + await self.for_all_player(lambda p: p.send_normal("*The night has begun*")) async def send_role(self): await self.for_all_player(lambda p: p.send_info(f"Your role: **{p.night_role.name()}**")) @@ -93,10 +91,9 @@ class Werewolf_game(Game): def remaining_time_string(self): t = int(self.start_time + self.discussion_time - time.time()) - return f"{t//60:02d}:{t%60:02d}" + return f"{t//60} minute(s) and {t%60} second(s)" async def discussion_timer(self): - # TODO: better? self.start_time = time.time() await self.send(f"You have {self.remaining_time_string()} to discuss") await asyncio.sleep(self.discussion_time / 2) @@ -125,8 +122,8 @@ class Werewolf_game(Game): p.vote.tally += 1 def who_dead(self): - maxi = max(c.tally for c in self.voting_list) # most votes - dead = [p for p in self.player_list if p.tally >= maxi] # who has most votes + maxi = max(c.tally for c in self.voting_list) + dead = [p for p in self.player_list if p.tally >= maxi] for d in dead: d.dead = True if d.day_role.is_role(Hunter): @@ -155,8 +152,10 @@ class Werewolf_game(Game): else: if tanner_dead: tanner_won = True + if werewolf_dead: village_won = True + else: if werewolf_dead: village_won = True @@ -187,29 +186,22 @@ class Werewolf_game(Game): if 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") # number of tanners dead + winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner") if len(winnning) == 0: winnning = ["No one"] - # TODO: better emoji code? - space = "<:space:705863033871663185>" 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 ":eyes:" - val = [ - won_emoji, - dead_emoji, - f"{p.tally}:ballot_box:", - f"role: {str(p.day_role)}", - f"(was: {str(p.night_role)})", - f":point_right: {str(p.vote)}" - ] - embed.add_field(name=str(p), value=space.join(v for v in val), inline=False) + dead_emoji = ":skull:" if p.dead else ":no_mouth:" + embed.add_field(name=str(p), value=f"{won_emoji} {dead_emoji} {p.tally}:ballot_box: role: {str(p.day_role)} (was: {str(p.night_role)}) :point_right: {str(p.vote)}", inline=False) embed.add_field(name="Middle cards", value=', '.join(r.name() for r in self.middle_card), inline=False) await self.channel.send(embed=embed) + def end(self): + self.running = False + async def round(self): try: self.check() @@ -224,5 +216,6 @@ class Werewolf_game(Game): await self.vote() self.tally() await self.result(*self.who_won(self.who_dead())) + await self.send("Round ended") finally: self.end() diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 94c966e..31a4a22 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -4,7 +4,7 @@ This is the main module of the Discord Bot Mainly loads the Cog's and starts the bot """ -__version__ = '0.4' +__version__ = '0.3' __author__ = 'Bibin Muttappillil' # standard library imports @@ -22,7 +22,7 @@ if TOKEN is None: print("Missing discord token!") exit(1) -bot = commands.Bot(command_prefix=commands.when_mentioned_or('$b ')) +bot = commands.Bot(command_prefix=commands.when_mentioned_or('$w ')) bot.load_extension('package.developer') bot.load_extension('package.games.werewolf.cog') @@ -32,7 +32,6 @@ bot.load_extension('package.games.werewolf.cog') @commands.is_owner() async def reload(ctx, extension): bot.reload_extension(f'package.{extension}') - print(f'package.{extension} reloaded') # checker annotations