diff --git a/src/package/__pycache__/developer.cpython-37.pyc b/src/package/__pycache__/developer.cpython-37.pyc new file mode 100644 index 0000000..96d4ba4 Binary files /dev/null and b/src/package/__pycache__/developer.cpython-37.pyc differ diff --git a/src/package/__pycache__/send_message.cpython-37.pyc b/src/package/__pycache__/send_message.cpython-37.pyc new file mode 100644 index 0000000..b5974d7 Binary files /dev/null and b/src/package/__pycache__/send_message.cpython-37.pyc differ diff --git a/src/developer.py b/src/package/developer.py similarity index 88% rename from src/developer.py rename to src/package/developer.py index 0ac7522..932b1e9 100644 --- a/src/developer.py +++ b/src/package/developer.py @@ -3,6 +3,7 @@ from discord.ext import commands class Developer(commands.Cog): + """This class is intended only for the developer, mainly for testing purposes""" def __init__(self, bot): self.bot = bot @@ -18,11 +19,10 @@ class Developer(commands.Cog): @commands.command() async def ping(self, ctx): - print("pong") + print("pong", self.bot.owner_id, await self.bot.application_info()) + print(await self.bot.is_owner(ctx.author)) await ctx.send("pong") - # developer commands - async def is_dev(ctx): if ctx.author.id == 461892912821698562: return True diff --git a/src/package/games/__pycache__/game.cpython-37.pyc b/src/package/games/__pycache__/game.cpython-37.pyc new file mode 100644 index 0000000..75ffe46 Binary files /dev/null and b/src/package/games/__pycache__/game.cpython-37.pyc differ diff --git a/src/package/games/__pycache__/game_cog.cpython-37.pyc b/src/package/games/__pycache__/game_cog.cpython-37.pyc new file mode 100644 index 0000000..b663aae Binary files /dev/null and b/src/package/games/__pycache__/game_cog.cpython-37.pyc differ diff --git a/src/package/games/game.py b/src/package/games/game.py new file mode 100644 index 0000000..69d7c84 --- /dev/null +++ b/src/package/games/game.py @@ -0,0 +1,18 @@ +from abc import ABC + + +class Game(ABC): + + name = "Game" + + def __init__(self, bot, channel): + self.bot = bot + self.channel = channel + self.running = False + self.player_list = [] + + 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 new file mode 100644 index 0000000..b530cff --- /dev/null +++ b/src/package/games/game_cog.py @@ -0,0 +1,90 @@ +"""This (abstract) module is a template for Discord Cog's for game specific channel commands""" + +# standard library imports +from typing import Dict + +# discord imports +import discord +from discord.ext import commands + +# local imports +from ..send_message import Send_message +from .game import Game + + +# TODO: take group as argument to add subcommands + +class Game_cog(Send_message, commands.Cog): + """This (abstract) class is are common function for the Game Cog's (setup-game, pre-game, in-game), mainly has checker functions""" + + def __init__(self, bot, game_instances: Dict[discord.TextChannel, Game]): + self.bot = bot + self.game_instances = game_instances + + async def setup_check(self, ctx): + if ctx.channel not in self.game_instances: + await self.send_wrong(ctx, f"The channel is not setup yet.") + return ctx.channel in self.game_instances + + async def not_running_check(self, ctx): + if self.game_instances[ctx.channel].running: + await self.send_wrong(ctx, "Sorry! A game is already running") + return not self.game_instances[ctx.channel].running + + async def running_check(self, ctx): + if not self.game_instances[ctx.channel].running: + await self.send_wrong(ctx, "No game is running") + return self.game_instances[ctx.channel].running + + +class Setup_game_cog(Game_cog): + """This (abstract) class is a template for Discord Cog's for game specific channel commands for setting up the channel""" + + async def setup(self, ctx, game: Game): + """This function creates an game instance for this channel""" + if ctx.channel in self.game_instances: + await self.send_wrong(ctx, f"A game '{game.name}' is already setup in this channel") + else: + self.game_instances[ctx.channel] = game(self.bot, ctx.channel) + await self.send_friendly(ctx, f"This channel can now play: {game.name}") + + async def reset(self, ctx): + """This function deletes the game instance for this channel""" + if self.setup_check(ctx): + del self.game_instances[ctx.channel] + + # TODO: better info message + async def info(self, ctx, game: Game): + """Send information about the subcommands for the game""" + embed = discord.Embed(title="How to play?", description="You will need to set up the game and its information in a channel and start the game there. Afterwards the player mainly interact with the bot in DM.", color=0x00ffff) + embed.set_author(name=f"With this bot you can play {game.name}") + # embed.set_thumbnail(url="https://images-na.ssl-images-amazon.com/images/I/717GrDtFKCL._AC_SL1000_.jpg") + embed.add_field(name="$w game setup", value="Make this channel playable.", inline=False) + embed.add_field(name="$w game players", value="Set mentioned users as players", inline=False) + embed.add_field(name="$w game roles", value="Set the roles to play with", inline=False) + embed.add_field(name="$w game start", value="Play one round", inline=False) + embed.set_footer(text="Have fun!") + await ctx.send(embed=embed) + + +class Pre_game_cog(Game_cog): + """This (abstract) class is a template for Discord Cog's for game specific channel commands for setting up the game rounds""" + async def cog_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) + + async def start(self, ctx): + self.game_instances[ctx.channel].game = self.bot.loop.create_task(self.game_instances[ctx.channel].round()) + await self.game_instances[ctx.channel].game + + +class In_game_goc(Game_cog): + """This (abstract) class is a template for Discord Cog's for game specific channel commands during the game""" + async def cog_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() + await self.send_friendly(ctx, "Game canceled") diff --git a/src/package/games/werewolf/__pycache__/cog.cpython-37.pyc b/src/package/games/werewolf/__pycache__/cog.cpython-37.pyc new file mode 100644 index 0000000..e3673ed Binary files /dev/null and b/src/package/games/werewolf/__pycache__/cog.cpython-37.pyc differ diff --git a/src/package/games/werewolf/__pycache__/game.cpython-37.pyc b/src/package/games/werewolf/__pycache__/game.cpython-37.pyc new file mode 100644 index 0000000..3217184 Binary files /dev/null and b/src/package/games/werewolf/__pycache__/game.cpython-37.pyc differ diff --git a/src/package/games/werewolf/__pycache__/players.cpython-37.pyc b/src/package/games/werewolf/__pycache__/players.cpython-37.pyc new file mode 100644 index 0000000..5173188 Binary files /dev/null and b/src/package/games/werewolf/__pycache__/players.cpython-37.pyc differ diff --git a/src/package/games/werewolf/__pycache__/roles.cpython-37.pyc b/src/package/games/werewolf/__pycache__/roles.cpython-37.pyc new file mode 100644 index 0000000..71a5402 Binary files /dev/null and b/src/package/games/werewolf/__pycache__/roles.cpython-37.pyc differ diff --git a/src/package/games/werewolf/cog.py b/src/package/games/werewolf/cog.py new file mode 100644 index 0000000..87618b2 --- /dev/null +++ b/src/package/games/werewolf/cog.py @@ -0,0 +1,34 @@ +# discord imports +from discord.ext import commands + +# local imports +from ..game_cog import Game_cog +from .game import Werewolf_game + + +class Werewolf_cog(Game_cog): + """This singleton class is a Discord Cog for the interaction in the 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? + await ctx.invoke(self.bot.get_command('werewolf info')) + + @werewolf.command() + async def info(self, ctx): + """Send information about the subcommands for the game""" + 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, Werewolf_game) + + @werewolf.command() + async def reset(self, ctx): + """This function deletes the game instance for this channel""" + await super().reset(ctx) + + +def setup(bot): + bot.add_cog(Werewolf_cog(bot, None)) diff --git a/src/werewolf_game.py b/src/package/games/werewolf/game.py similarity index 96% rename from src/werewolf_game.py rename to src/package/games/werewolf/game.py index 53ccf39..4526027 100644 --- a/src/werewolf_game.py +++ b/src/package/games/werewolf/game.py @@ -2,11 +2,13 @@ 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 -from werewolf_players import Player, No_player +from .roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role +from .players import Player, No_player -class Game: +class Werewolf_game: + + name = "One Night Ultimate Werewolf" def __init__(self, bot, channel): self.running = False diff --git a/src/werewolf_players.py b/src/package/games/werewolf/players.py similarity index 100% rename from src/werewolf_players.py rename to src/package/games/werewolf/players.py diff --git a/src/werewolf_roles.py b/src/package/games/werewolf/roles.py similarity index 99% rename from src/werewolf_roles.py rename to src/package/games/werewolf/roles.py index f7cf685..8e9b867 100644 --- a/src/werewolf_roles.py +++ b/src/package/games/werewolf/roles.py @@ -1,6 +1,6 @@ import functools from fuzzywuzzy import fuzz -from werewolf_players import No_player +from .players import No_player class Role: diff --git a/src/package/send_message.py b/src/package/send_message.py new file mode 100644 index 0000000..136f7de --- /dev/null +++ b/src/package/send_message.py @@ -0,0 +1,18 @@ +# discord import +import discord + + +class Send_message: + """This (abstract) class for sending formatted messages""" + + def __init__(self, bot): + self.bot = bot + + async def send_embed(self, ctx, desc, color): + await ctx.send(embed=discord.Embed(description=desc, color=color)) + + async def send_friendly(self, ctx, desc): + await self.send_embed(ctx, desc, 0x00ff00) + + async def send_wrong(self, ctx, desc): + await self.send_embed(ctx, desc, 0xff8000) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 4bcd478..c4b0ca0 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -1,124 +1,43 @@ +""" +This is the main module of the Discord Bot + +Mainly loads the Cog's and starts the bot +""" + +__version__ = '0.3' +__author__ = 'Bibin Muttappillil' + +# standard library imports import os from dotenv import load_dotenv -import functools -import asyncio -import discord + +# discord imports from discord.ext import commands -from werewolf_game import Game as Werewolf_Game +# Token stuff load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') if TOKEN is None: print("Missing discord token!") exit(1) +bot = commands.Bot(command_prefix=commands.when_mentioned_or('$w ')) -PREFIX = '$w ' -bot = commands.Bot(command_prefix=commands.when_mentioned_or(PREFIX)) -bot.remove_command('help') - -bot.load_extension('developer') - - -@bot.command() -async def load(ctx, extension): - bot.load_extension(f'{extension}') - - -@bot.command() -async def unload(ctx, extension): - bot.unload_extension(f'{extension}') +bot.load_extension('package.developer') +bot.load_extension('package.games.werewolf.cog') @bot.command() +@commands.is_owner() async def reload(ctx, extension): bot.reload_extension(f'{extension}') -# TODO: better help message -@bot.command() -async def help(ctx): - embed = discord.Embed(title="How to play?", description="You will need to set up the game and its information in a channel and start the game there. Afterwards the player mainly interact with the bot in DM.", color=0x00ffff) - embed.set_author(name="With this bot you can play One Night Ultimate Werewolf") - # embed.set_thumbnail(url="https://images-na.ssl-images-amazon.com/images/I/717GrDtFKCL._AC_SL1000_.jpg") - embed.add_field(name="$w game setup", value="Make this channel playable.", inline=False) - embed.add_field(name="$w game players", value="Set mentioned users as players", inline=False) - embed.add_field(name="$w game roles", value="Set the roles to play with", inline=False) - embed.add_field(name="$w game start", value="Play one round", inline=False) - embed.set_footer(text="Have fun!") - await ctx.send(embed=embed) - - -# TODO: interaction COG -async def send_embed(ctx, desc, color): - await ctx.send(embed=discord.Embed(description=desc, color=color)) - - -async def send_friendly(ctx, desc): - await send_embed(ctx, desc, 0x00ff00) - - -async def send_wrong(ctx, desc): - await send_embed(ctx, desc, 0xff8000) - - -# TODO: (general) game COG -# game commands - -game_instances = {} - - -@bot.group() -async def game(ctx): - if ctx.invoked_subcommand is None: - await send_wrong(ctx, 'Invalid sub command passed...') - - -@game.command() -async def setup(ctx): - if ctx.channel in game_instances: - await send_wrong(ctx, "Game already setup in this channel") - else: - game_instances[ctx.channel] = Werewolf_Game(bot, ctx.channel) - await send_friendly(ctx, "This channel can now play Werewolf") - - # checker annotations # TODO: replace with discord.py error handling? -def channel_setup(command): - @functools.wraps(command) - 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") - 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, *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, *args, **kwargs): @@ -129,30 +48,9 @@ def error_handling(command): except asyncio.TimeoutError: await send_wrong(ctx, "Error: I got bored waiting for your input") return wrapper +''' - -@game.command() -@game_not_running -@error_handling -async def start(ctx): - 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) - +''' # TODO: (specifig game) werewolf COG @@ -175,10 +73,6 @@ async def minutes(ctx, i): @error_handling async def time(ctx): await send_friendly(ctx, game_instances[ctx.channel].remaining_time_string()) - - -# TODO: developer COG -# smaller commands - +''' bot.run(TOKEN)