From ec446785ee56b2ab5f1101b7c5e1ce20870ec035 Mon Sep 17 00:00:00 2001 From: bibin Date: Sun, 12 Jul 2020 13:17:39 +0200 Subject: [PATCH 1/9] forgotten commits --- src/werewolf_bot.py | 17 ++++++++++++++--- src/werewolf_game.py | 2 +- src/werewolf_players.py | 2 +- src/werewolf_roles.py | 31 ++++++++++++++++++------------- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index bfaf7cb..84b7b4b 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -158,6 +158,7 @@ async def time(ctx): await send_friendly(ctx, game_instances[ctx.channel].remaining_time_string()) +# TODO: developer COG # smaller commands @bot.command() @@ -193,8 +194,18 @@ async def logout(ctx): @bot.command() @developer async def debug(ctx, *args): - print("DEBUG") - print(ctx.args) - print(ctx.kwargs) + 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) + bot.run(TOKEN) diff --git a/src/werewolf_game.py b/src/werewolf_game.py index 255bef0..f564f2c 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -14,7 +14,7 @@ class Game: self.channel = channel self.player_list = [] self.role_list = [] - self.discussion_time = 300 # seconds + self.discussion_time = 301 # seconds async def send(self, message): await self.channel.send(embed=discord.Embed(description=message, color=0x00ffff)) diff --git a/src/werewolf_players.py b/src/werewolf_players.py index 13af25b..285844a 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -59,7 +59,7 @@ class Player: while True: def check(choice): return choice.channel == self.dm and choice.author == self.member - choice = (await self.game.bot.wait_for('message', timeout=30.0, check=check)).content.split() + choice = (await self.game.bot.wait_for('message', check=check)).content.split() if not len(choice) == n_ans: await self.send_wrong(f"Please give {n_ans} numbers not {len(choice)}") diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py index ce14fb1..f7cf685 100644 --- a/src/werewolf_roles.py +++ b/src/werewolf_roles.py @@ -45,28 +45,33 @@ class Doppelganger(Role): @Role.no_player async def send_copy_info(self): self.copy_role = type(self.player.other()[self.choice].day_role) - await self.send_info(f"You copied: {self.copy_role}") + await self.player.send_info(f"You copied: {self.copy_role.name()}") @Role.no_player async def simulate(self): if self.copy_role in [Werewolf, Mason]: self.copy_role.add_yourself(self) - if self.copy_role == Werewolf: - await self.copy_role.phase(self) - if self.copy_role in [Mason, Minion]: - await self.copy_role.send_info(self) - - if self.copy_role in [Seer, Robber, Troublemaker, Drunk]: + elif self.copy_role in [Seer, Robber, Troublemaker, Drunk]: await self.copy_role.query(self) if self.copy_role in [Robber, Troublemaker, Drunk]: - self.copy_role.simulate(self) + await self.copy_role.simulate(self) if self.copy_role in [Seer, Robber]: await self.copy_role.send_info(self) + @Role.no_player + async def phase(self): + if self.copy_role == Werewolf: + await self.copy_role.phase(self) + + @Role.no_player + async def send_info(self): + if self.copy_role in [Mason, Minion]: + await self.copy_role.send_info(self) + @Role.no_player async def insomniac(self): if self.copy_role == Insomniac: - self.copy_role.send_info(self) + await self.copy_role.send_info(self) def is_role(self, cls): return self.copy_role == cls @@ -113,10 +118,10 @@ class Seer(Role): @Role.no_player async def send_info(self): if self.choice < len(self.player.other()): - await self.player.send_info(self.player.other()[self.choice].night_role) + await self.player.send_info(f"You saw: {self.player.other()[self.choice].night_role.name()}") else: a, b = [(0, 1), (1, 2), (0, 2)][self.choice - len(self.player.other())] - await self.player.send_info(f"{self.game.middle_card[a]} {self.game.middle_card[b]}") + await self.player.send_info(f"You saw: {self.game.middle_card[a]} {self.game.middle_card[b]}") class Robber(Role): @@ -130,7 +135,7 @@ class Robber(Role): @Role.no_player async def send_info(self): - await self.player.send_info(f"You robbed: {self.player.day_role}") + await self.player.send_info(f"You robbed: {self.player.day_role.name()}") class Troublemaker(Role): @@ -156,7 +161,7 @@ class Drunk(Role): class Insomniac(Role): @Role.no_player async def send_info(self): - await self.player.send_info(f"You are: {self.player.day_role}") + await self.player.send_info(f"You are: {self.player.day_role.name()}") class Villager(Role): From a4652642d15e9456b9ffa2ee8da3a3e571c8b746 Mon Sep 17 00:00:00 2001 From: bibin Date: Sun, 12 Jul 2020 15:53:49 +0200 Subject: [PATCH 2/9] added TODO's --- src/werewolf_game.py | 1 + src/werewolf_players.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/werewolf_game.py b/src/werewolf_game.py index f564f2c..53ccf39 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -69,6 +69,7 @@ class Game: await self.for_all_player(lambda p: p.send_info(f"Your role: **{p.night_role.name()}**")) async def night_phases(self): + # TODO: implement waiting if role in middle await asyncio.gather(*[self.role[r].query() for r in [Doppelganger, Seer, Robber, Troublemaker, Drunk]]) # slow await self.role[Doppelganger].send_copy_info() await self.role[Doppelganger].simulate() # slow diff --git a/src/werewolf_players.py b/src/werewolf_players.py index 285844a..77f1589 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -49,6 +49,7 @@ class Player: text = f"{question}\n" + f"{'='*len(question)}\n\n" + '\n'.join(f"[{str(i)}]({str(options[i])})" for i in range(len(options))) await self.dm.send(f"```md\n{text}```") + # TODO: Basic Converters (https://discordpy.readthedocs.io/en/latest/ext/commands/commands.html#basic-converters) def check_num(self, choice, N): if not choice.isdigit(): raise ValueError(f"Your choice {choice} is not a number") From 9cdc8d742cc5e72e2126045d23b0d91aed7fbf06 Mon Sep 17 00:00:00 2001 From: bibin Date: Sun, 12 Jul 2020 15:54:17 +0200 Subject: [PATCH 3/9] added dev cog --- src/developer.py | 59 +++++++++++++++++++++++++++++++++++ src/werewolf_bot.py | 75 +++++++++++++++------------------------------ 2 files changed, 83 insertions(+), 51 deletions(-) create mode 100644 src/developer.py diff --git a/src/developer.py b/src/developer.py new file mode 100644 index 0000000..0ac7522 --- /dev/null +++ b/src/developer.py @@ -0,0 +1,59 @@ +import discord +from discord.ext import commands + + +class Developer(commands.Cog): + + def __init__(self, bot): + self.bot = bot + + @commands.Cog.listener() + async def on_ready(self): + await self.bot.change_presence(status=discord.Status.online, activity=discord.Game('One Night Ultimate Werewolf')) + print('We have logged in as {0.user}'.format(self.bot)) + + @commands.command() + async def hello(self, ctx): + await ctx.send(f"Hello {ctx.message.author.name} :wave:") + + @commands.command() + async def ping(self, ctx): + print("pong") + await ctx.send("pong") + + # developer commands + + async def is_dev(ctx): + if ctx.author.id == 461892912821698562: + return True + await ctx.send("This command is not for you!") + return False + + @commands.command() + @commands.check(is_dev) + async def logout(self, ctx): + await self.bot.logout() + + @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) + + @debug.error + async def debug_error(self, ctx, error): + await ctx.send(error) + + +def setup(bot): + bot.add_cog(Developer(bot)) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 84b7b4b..4bcd478 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -18,13 +18,25 @@ PREFIX = '$w ' bot = commands.Bot(command_prefix=commands.when_mentioned_or(PREFIX)) bot.remove_command('help') - -@bot.event -async def on_ready(): - await bot.change_presence(status=discord.Status.online, activity=discord.Game('One Night Ultimate Werewolf')) - print('We have logged in as {0.user}'.format(bot)) +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.command() +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) @@ -38,6 +50,7 @@ async def help(ctx): 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)) @@ -50,6 +63,7 @@ async def send_wrong(ctx, desc): await send_embed(ctx, desc, 0xff8000) +# TODO: (general) game COG # game commands game_instances = {} @@ -70,6 +84,9 @@ async def setup(ctx): 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): @@ -137,6 +154,8 @@ async def players(ctx): await game_instances[ctx.channel].set_players(ctx.message) +# TODO: (specifig game) werewolf COG + @game.command() @game_not_running @error_handling @@ -161,51 +180,5 @@ async def time(ctx): # TODO: developer COG # smaller commands -@bot.command() -async def hello(ctx): - await send_friendly(ctx, f"Hello {ctx.message.author.name} :wave:") - - -@bot.command() -async def ping(ctx): - print("pong") - await send_friendly(ctx, "pong") - - -# developer commands - -def developer(command): - @functools.wraps(command) - async def wrapper(ctx, *args, **kwargs): - DEV_ID = 461892912821698562 - if ctx.author.id == DEV_ID: - await command(ctx, *args, **kwargs) - else: - await send_wrong(ctx, "This command is not for you!") - return wrapper - - -@bot.command() -@developer -async def logout(ctx): - await bot.logout() - - -@bot.command() -@developer -async def debug(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) - bot.run(TOKEN) From eff057da8c9d77255c3a324dc1f3ea657ad2e1c6 Mon Sep 17 00:00:00 2001 From: bibin Date: Mon, 13 Jul 2020 23:47:56 +0200 Subject: [PATCH 4/9] lot's of refactoring and packaging --- .../__pycache__/developer.cpython-37.pyc | Bin 0 -> 2695 bytes .../__pycache__/send_message.cpython-37.pyc | Bin 0 -> 1068 bytes src/{ => package}/developer.py | 6 +- .../games/__pycache__/game.cpython-37.pyc | Bin 0 -> 751 bytes .../games/__pycache__/game_cog.cpython-37.pyc | Bin 0 -> 4787 bytes src/package/games/game.py | 18 +++ src/package/games/game_cog.py | 90 +++++++++++ .../werewolf/__pycache__/cog.cpython-37.pyc | Bin 0 -> 1597 bytes .../werewolf/__pycache__/game.cpython-37.pyc | Bin 0 -> 10956 bytes .../__pycache__/players.cpython-37.pyc | Bin 0 -> 5114 bytes .../werewolf/__pycache__/roles.cpython-37.pyc | Bin 0 -> 8612 bytes src/package/games/werewolf/cog.py | 34 ++++ .../games/werewolf/game.py} | 8 +- .../games/werewolf/players.py} | 0 .../games/werewolf/roles.py} | 2 +- src/package/send_message.py | 18 +++ src/werewolf_bot.py | 148 +++--------------- 17 files changed, 190 insertions(+), 134 deletions(-) create mode 100644 src/package/__pycache__/developer.cpython-37.pyc create mode 100644 src/package/__pycache__/send_message.cpython-37.pyc rename src/{ => package}/developer.py (88%) create mode 100644 src/package/games/__pycache__/game.cpython-37.pyc create mode 100644 src/package/games/__pycache__/game_cog.cpython-37.pyc create mode 100644 src/package/games/game.py create mode 100644 src/package/games/game_cog.py create mode 100644 src/package/games/werewolf/__pycache__/cog.cpython-37.pyc create mode 100644 src/package/games/werewolf/__pycache__/game.cpython-37.pyc create mode 100644 src/package/games/werewolf/__pycache__/players.cpython-37.pyc create mode 100644 src/package/games/werewolf/__pycache__/roles.cpython-37.pyc create mode 100644 src/package/games/werewolf/cog.py rename src/{werewolf_game.py => package/games/werewolf/game.py} (96%) rename src/{werewolf_players.py => package/games/werewolf/players.py} (100%) rename src/{werewolf_roles.py => package/games/werewolf/roles.py} (99%) create mode 100644 src/package/send_message.py diff --git a/src/package/__pycache__/developer.cpython-37.pyc b/src/package/__pycache__/developer.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96d4ba4c2155bcf82fb38466472a322b1847f2b1 GIT binary patch literal 2695 zcmZuzPj4GV6yMo@UMEiCrY%%ZU_l_&h&C;ue^{XkDA0nCIRvP%gjVaFiM`Hx$DJ7` z&ElNW6H;%83##NCx$zx1aNrxvl~cb$FXg>i$8oBzHM6rbZ{EE3e((LppRcSeS@8Vy zZzKNQx2*s0*K1*5TWyHS@hR`wnQ`d@s`_x(quT)jaeotZ_tWgRbB@Xjpw=WtFbYR@T_sph=tb zjL~$4u7d|>b^Qyr%W^(qqWtFjekQ{-PoxZCkQIs*l+lnE`F^;~MX35Lq{rePgu^78 zuP~{yq8Eon$$IwL zMxPJaMmOtb#m3fmUtj)~C6tMcUZytom|%N6-({C!_l6Yd#wba5k{;Wbn|EcjuY)+w zicH0EfTtUiNVz_D_{#BeN?;uye0$LTh9*CPZek6r0Z{<{z*Y7XV25DakL|C)gL@#Z zeCkYVD0KwECzW4ryL|n-f`#p@*H_^Wxypu#V&VPD=xy-Umnud{*>0xx%Qx<` zu%GO*Fz3A<#5^m)M25dyy)u?e9JB%x8oieGlcLAskzkS)DbqEY$&?GK{jEx)4`5{l z)9&45$h0>SfTH#RJTRd}1UhHJTXi+~)B!JI0kPd*Mz0Y9%MMS)=~&Kx2TT(InmA(m=tLDgJ#ve*3GIV=UKpMl0*dOAYtI zPpHJ#K(oZ!vq*!uq)E!=iSd=lDU=XNV?O$f=1<8nHnY+K%CM_2`%ox+4AfEO^h?4g zLePS($`EJq@M`n?@H$AXLHh@#|AJ1L$RSNXwh!P6H~@k^0O~&gVm_!%oPn(z8XS^| z`-e5DLGxyB7}GkmrHK#yvgsS9UwN`Zfs7wRl0kC06Nfe*&(jIg%cW;duzC1CCg8yT z!ut8mg8)}2!O5zsO@aZOPOJyN-mf@ky$HDBR=4+u8Hym|NOYnaY6(Ql4{B z29Xkc)ZdRvKT<&1(KAvnU;iYMK%XqSdG-3O8=F@*H*ej%cH_q8wOiM3Yqt%B_!RT1 zY((7zYEH#n{xB+61Vi zgwKY2kjbhH--J!D*Ki%?g?1Cslj0H#J9P+7Y=k>Y6iBZcYeywr?U^v3EuibgTJ$LE zj(hKc4v8*U&MIVd_4|~plcv3j`Mp3wMn1*J;e&5%e?yZdy@4?&_6er*8?mY@XTf}r z3BkpS1}&QdVzYJ~wa$3({7H!|NVHq)cpW0G#<5(shvB#r-M9`@OJ=CTgSQD?Ej9qfMRc8_ZH53X`iJ#rt$>-sS=ym;Z3?=zF|4M zdIUxUVKN~Nh-(MVy4Q4@e&GA$oC|Hu$ebcMv=2cLcHIvk0vKq77a z1C;y?_zP5SsrU;tVP-ET7m)%n*1Wv4v)?znkNW)%LwWtZC%oW8(OvQ-lgJ29;)9siVq8|SSNm4y6GN=OFcYBr^(5IZXF3^Y+}#K0S5?2O@^m>XCY3kSrP}xeYkkE4IEtgR?9@2>HWSPlQwb~nf8J>f; zm@6kX!#~U2jYysfdLwce*6?uL$;H4;uCfp+NSN76>H*RVS;fLIt@V#*j6)wY8=XncG?0 z*|wT(`}*EE-FJR0WKiDpUmUY_DZx*0Ihm(rc{^-Ox$f4TEpI-5Jo{v7G_GvBvAi?R z+ztJAb4Fh%y=%&C-F&TEQ?@j-k9fYl54qNz?MAJ+qh|myiO<7=*_|Krjo=&!Th?Es zo@a`5#465I2$xYP`UiMM$B?>VXYn~#@PoC=NGvKagf3EenUETl7D7KFOIX7dNGw@zcfH^t7Zl)iXmM1F7C%KH#6+EHTg9d1?+zop=WWKl%;jGa_m{T- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b663aaef0710c1a22afa4513f4559cd9f6fd7c43 GIT binary patch literal 4787 zcmcIoOK;rP73Omuni;(;JF$(pbQ-{(z#gSYfdqlu+L0UEKr#s+BZX&Cg5tfBIO34g zOO6#0la-C`40O{~8^BujclbASQEt1+w##nP?;LVo@>nU*W&~c|7wUDji|bZs_iXgpVP#P5 zRnb=w)vz|G_v)zEL_J&?GzUbGMSE4O zp}ofKHMG~o2HG3kUKiG`*51sXJU$E(=cc!pq{{Qtwlj#uD3lI5JtvieVd$mO*^iZT zKS=yo31=rhxRG$rfj5v&GL(L>ANY=c=tYqX9X}onyhtS91F+WCbMxM(J9t_513&Ge zQkzaNiV4kK83}hFlf*la-|I!E^#F_ew8Z3RqH-5cLWR_NI<#pBy=O{G+Wm^G?i-gi zVf+<371ndBR};3VJlA@4Q57}3S43T`;N1`nAaCY1+QIeXgY4V0I`Bg;NmvC+GM+ec z_D7MQ22_DA(nIOc=cO`kCNdojZ<9W5IYTAOS`cxieajhmK@^@ihh73TN&iTy>3RuN zvPxgNL6oFk{G1mLqSnKOY zRdsEwy)?$U&`*s=aE^zXJ{zq7ozczSc??xgObbRC#i_&Ec1~p40pMym$Z+$Ogly?p z#nD0AP)%%?n|}HYE1@XhpjzJGC0ss>%OqF@g{C+4O+DM1Vwsz?9L@*$2vA69{j%0K zlmkHK89;O6O*%4wa&}{-POdxm9MUiwTBvX!Cyp9LQ2^O3hNI}r^J>|tHqa_jsZG?+ zqS}CR+;Vb(?HXp4*j5SKl_|E@rY=X=!2lWb*^``6oVdRD7NY!;k?7OZa7t5=nh;4b}vHT|8DFT_Q zk(6sxT%dw1UR|PsJZi2Wi&AwIbS4S;VNJJ;x2>98v$@|#*Yrml-o=yr62)Q|n+slc z8N4de;$8}2R}g>3;XJ_z&V@U&93r3 z<;RYX^`rRML1sMp#A#R9Neg!+lg=$NF^<4_i=)xL3J{dSleWnLsc>EL%riEW+r0lR z(jVELW;FF{#wA3g#pCYqv;}`9qhh_kjSBPz-Jj}etJJ0OD=-c6%q2<;0y1w#d|n{3m4u9B;6;isR$d3eQ>Mm48G@)1gZLvVDa?SV5+(B& z=C|h9>>I+Kn1ZOh(2*<5zD3EM68j6YY&SrI=G!gMp!xP1Xwa-}iw&{KYw7*U*c2CD z=*UD{WR_wJD!Z6{NbCfPLE0a9OaQ#Sc$BivlF{CT%uHzk=`m|Qgn^_n+sJRSKR<~_ z&T$ZiP9&wEerRXnPONwEB_~KhIp(&38F~|og-r_wN27{;`5PRXk{##Xek#?mr$lj# zWR+kSCE*An41(5S_wb`c)GV2%FbsnBKjt`pL*d)<`s~l zAd=bIWQjufB)hf?{S0J8vOr;@L@Ky-!YiRV+f&7%OtLF9ootw!6TLI$*;LkEw)z#x zj#V*t6p5_<(0dAcJBqGTJWCP%@wVk)Pl|54s;;uMzojNoi`4Z-=^=7Aqo{jAxch+& z%d!19hPH`;MFI(&$j6+ox2ZYH985zjI`2;~z~oLd*7Yqs#a~m;E&$hDnD5Z+Zo8)5 zqNT{w6>%4Jg$hnq*Qxd{728zIcH+{|4rX2kmcpO@Gv_*9eSv5{EEBsRvI zs3FgsQd}ewJXxpNSIc|Sc~Vx($)C^3xV0c-IblJC1EM<8}I4a<^md?K<>KC zF@Av$)rVAkM8z_nAR7Mw&%`J6npray^)B}b2P5bE1dOVhHy%ZWD;&grcKDOIf*2`c zXOkShaHb|CeT66aFKnNv?K$V5NReODTBX%roP+lUqo0}-MUk1&_l=k4Dc$w7mmq!f zw?8(2T##B1h>alE_+TiaZO^QR%_XlCL(0Lr=g5p$oW{c^By?dqxB{)ui%r8_P`Ec5 z4h83>HuEtC9@D}^c@alpGm84!8yt^w)7$eEX)3RzCqo<%-!zJZ(kQPR9i$sYg5^yn zilu8ST9R>cRL{+w_<-*b9H!YWd8=h|<+VG-jpx_&e-u7;%WUdRvxWz6d+V+BYwNB5 E0M2EN=Kufz literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e3673ed4b1c9e330168b7b486239048bf91f7941 GIT binary patch literal 1597 zcmbVMKabNe6t|PKN!t`S{s{?&Fm`|p3@{*s0OX^txh1{~ROo}Kr8zu$ZIlYYNTpuPI$voAIw-|?eq4on_H zS2%FOX-0AyQA)VQ?aa#U$cDMYTbYx$q825)gtxhSM7S&Ll^wOY$2&(Pa(Q=;40@;F zg$$_nk}}WZf~!+&W7v&z!IE-vN^3*^t&rlN%*Jd2D+&JBG{l5WaTo-NC?}D{X=DrM zT?=u2qKN2-tZ390?X`PCj>(uBTprlGb2dbWw$3?Qm-mdVdrTY>^#Hf8{WCa#aWQ-| zO;w=MVv>ofEP^D9l?uQV2hUQKl#&Ne%SkXUWl&8;kQSAYaZ;tQH7$(kV8bpzc5gtD z>uK?!+!yR1t)}I?0x66@3F?Cl-!150p{rXkEa{po$ve6R>L>8Lfb={kkPHHVt>L17 z79uUi<-peMI)b)GWuX-A1{tnH*a{7mEp@c!dvI4yG#j?z(DgnO@F+<7Hp zE@U`Kt8nA_gSZ2(LM4-M7AO1hM1;sjHS6OLw7NU{sJ#Y?kKhd>dvWb;lVIG{?AYRq ziC*C^H(;n>x~5B7Stk~7rG_g-u8VtnqTrw`=$*$#<#+@d8llyE)YRHpJ`m*)6ltmX zOi1mRKid#vU`d21`#7h%!{ShkFf$` zGN!wXuL)hiS`7qHf;FDv-}jg!T?LMCKX-6*-3_MR&zvWoSkoroyYJfZea@+J90d`xz*_jVJs*5~)ei(Ym501eNuUUeh@@ zY6jz6JjFh=s)!5WdT({L<1IfG z)G!m99|s*Tu3Qf`H@(hU!(a1M?A-H|w-t0&WBXRiZv}pA-D-r$%sU=l+aCujE66K% zRnS}Mc-_VZYSwkt^Ecw+O+O5}eyh=p?Yj-%r~V)G{0MnrIjAeFR@GzsqfTQ7O+_?q zazLYoX$(NBY%5(B-$dAbL@)hJq+%icr{tSJ z#@FMS48ZlZM%U|~y6t=Ja%*ipa=+7wTHQwExmk}UkrMqVC%C+eC%k|}ih)+w29%0{ zj#OXMkQ$ttl3FaOVLfB{M(YyLUA5x^aOAiAwb)n*qPWmpCk%Dsa)N_;rxiwVk#Lk0 z=VU8v_QDV(P{+Q!su7!^*I89Zu)aEq|i6-pf zzkf7?4MXp8*11X0p&#WIp3m@vFCiIdZLKXLy=@GH6jFZ-Dtat#f*C*1h$R6dbGh}! z7XXnSm!%grRckY1x^D)ZK!I;8ue;*Os>z2YAowya5Oe}1W9vPBsjHJ%EH2KK=D^X03Z0MmrmP{;;@cnXbh$8WZR*j{Ty>z-1_FjqB! zn|cN1WHTZu6V&3_{KUu6mI3heFWNy4lm(1pP=0{I7#KxcBQPdFd;~svr0s(?q`41~ zD(#2bBY>TH9EhK4PxOO4%@p$BwlVi#)Ar6FVQ?Sa6_K_t2AK9hPZmi4Ulq$Z^&#}D znIt_?`5IM>uGg&!CZiDo6frx&B7^*;{JBFfN}(Q31AD)i$l7qp?dz|(ISMgoR(gn) zK_9cb*zCS`pnU?qxDO=-&cIed6FxXK-K9E@&dK(O^%ZUH^WxPt&-b=B)wQpn$~!f+ z{%bF?(`;bXjGeCM6W@hQ7|b!T*$!HMN+Yq+4c9UnA!g&nk?fNJ2aWmk{0|Ljjk4!8D;KG(pnjMQMV>&>cJsV5SLxIH8GyaVEt1XwR_) z+234G0UZl90cIuzm*z{E@YQF}N78#bArwf}|2kx#Uc=HE6?`4#$?aW28;Q9L{}+Gx z_J-Iu6!A9WTwOsaMR~0p_SV+CFd+*jxFOZWMM69u_^Y&Pg7$gQKlYq;b4VYmh;LwQ z#9~p3Mc8M@fO4o*76m{yWbR4rrJ0*np&w`i2N!_jRE~5PaEN5_Wsy`$=zOZ#YVt$FvPSY@@Hm zeen*Eu}9-%;fX~fS#&tdinj2S?BxEboBo4FrzKs#2W_d`VAV~m4p8J%hTw^3doXSx zxNLW;6|EEa(lHPgM#uA6PQ;~88l9f^o>GBgBFc%-5w%#&b+5TWY9@4$kbqbe$3_2e zo?5uq5QelfqAP}BghB*OLiDkHEzHlfJE!UJ$mCr#XW6JFuj$wkD{f7*($sGK;JKOzdyd0m?sdzmDrT6W)zzQ zlSEp~*6U$!rP*nOp%>QcY98BDgm6XYiz^K&>pMYDvB^fNRxMC_`9ToD224ymOL?H1 zs*xxiV(M5lC+W{Zm)<6EH`@}LaP(Q>2whY}MV}Y*$S0x5BXUqAej{jx3Q5GObq^lV z_Vq}Q3@BQ24|JJ8lhp1&oIoFHr_!Fjt7qkXYXDU&G(gE_D4o1FrqpRH#I^S4`Uw`( zQ0s-%#2aqsxz-V>>S@*yt7k>f${q%po z_~MJIqi6=E;Mh{^iY-wCVXjsHKuLb9w^57B-IkQF(whw>Q{2U(Rda|o>cL8z5twb2 zb{Wr4?&EjRn%dSS@WIgpi)ViE=7$g@z9zQ#jAkLh4RM)j&IutoW|Yz(q5#&2BovZC z2Lfh>zT7`@@h+sM&wkK)BXn21wVr>mDxd;j8`u3nbsHTVYKr|4J6Ai6?uu+&yN+r9 z2andD)QwR?MRlHi90kcJ%=#+EWDuJWzvXZ=mgNB9Nqg4181?$~;WV@yM!+RmvacVI zF49RCv1`|#f!>fqWAHrZzJhf z^JKuY(GtHZ6EDXSLwQF;lF7Tvd0I)xZ4(SU-3%QfITOMx4aQy6#044Oov2wn zVY;zcY)xn=7Q)M;O?|0F)GbVqo&EPwp0>#C(IVA(Ri$1RDItDUMM_ApsWK%LxnQ`~ zW|@PU!R8J#Wz7j&-ChTAo{L_VJ!9oC?eR;?P6tm&qAQ{iCOZ zV_;(l8OGOBilS5t>O08PT}sYULd3&d7h7S+^EMex8B!T0fyZhet*lsrbrq=;mlkS7#6!>0T*n~|4_M8UY`0yC5 ztb(J%bx(FMHuV(GgJ9s}oXtaJ%9tT|GEUy3NhofD&>{@v(GgKkBD!>?;f&f-U!`CA z1hn9`eE0gT+@u6^R_n7U5FJTU>f(T|c_5jO-&+S~1upzPcdOwe5)rr?txo56t9q^2 zY=jY0iTV}>r-YW{9@g+=3r2VnQahJFG?C~OBGVK8oV22|$yr~SG?u)-QKV)f2u>Y_ zr_IU!a~QUL211S;?nRRKZ3HpexE1;)g57ofzLUDj`ZC93;1#jQD={v%B7~;NtRZrk zhJWSZ#REL)&YtyWd4}QE40I(#P#%64Nt8N2WTTTwx+i{^_@oFXkK*;aV*4Tm7C(Jy z7ZH?!UN;7M8?rlsTveB0_Y_bEEg(X084&`q@a?=&M=%NLNwhOX^Z|*PlHD=sT|o07+Zb*cCgj|djScS??pBM44>z`%wv)h+Nr#9X7!>SZ#D!LvI6`w+ zBDNd2^Yo=663GG}>-0iM-Vtbb71boh>|{&#(G(KzX>3=Oa4%JX?>rfd%eoIkj${lH z$q+ii(-04^WI!fTY#_7|A?8dsn@Q9e_KblRTndl1m}s%MCB6JXFAHr@`eVIpv=k^8BC85J-sEui#s13IjaA%0t_Hna!Ao4dBh9YIASYHyZGF~8&K8+Xt0__V?;s(GDU}o4UmE&ic%ygs+B~eP`Os{6QNlvNBrFbOVHZPR{AYP zQk@@GTRvZ3mqOqMUID|3!LqQ{Znv(ZN+`xZVS zp)+XhIXcCG$aw0~aQC-%31d%LP%M z%|58GqE>{sHQmtloux2u-s z7nfFGxCK#tCD>ka(;z3-zOdB@aVvDF>bgstxE{tOAVrOr+#5JO8QZaiyQZEO+bzG- z!p17MI9~2jygdBU*WfR7|M@Q?aKeCaQL8Sf4ce0t_PQ)%)Jv$Umnor}&P3V-RATd^ zNcT3YEw3XnBvNcPB{*HCK4h+__bH)U`y}R4O61TcHQc9!tZbgG=4`cm{^p1G!lEKB z0zY-H8?;+-iAyEnbC7B=!;s3Rs#2zQLf-Y2=`zt zRE?HdIp*?``+_zEb%pj_lwkxJ%Xj}s`=C6GX1g2!04S;`=CcN|Oz;P?{P+JO|9O{ihCJ(5=1omusa z5ZW50QV~_*g$F;UR3$wo&-n-Zi+RXP^1@%BQboSgGqV?6Dv4)nx_kQc<@C9H=k(s3 zn5Zy3C;nY=+vAM=lRk>aMB@hD=n@LaByY0@`;!t@ zvK*IH)MIi&PNJ^JH{=xRaXBqdpsvc3at8H;oRz0gPs-DB4)q)Ij692aO1>%2p`MmC zd0xKtls8UDN4|}olk$Rm2lb4cm+zvUmG8;-QJ<0*ah6M|{dL>h@>NrSaeC+^)P8|C z`T#}DcG$K^SR!1WuvO7LpRhe+pM4IQeMY@z!ckg@zLcr%G4NIuj&gcVaH!sY%R8eg}YyUe)&t^ zlfGJ5Zp90mzVbK2_GAC@Vi+$(s=3hhnrq&&zd#E{1$}9utB)34>29TF$6NDltQ={B z5H`2@DZIV$?EF`V-WLL$fVjFv6Q)ok+++!J>4j>ugNn2)y)9RTZ9lC9t>u+iw`(Fb zBfq_rnncm3$qL#yEk%BO2mO<@vX(jTP3A1*q$UTA9;*9XoWh5M0$doV=7riq^J3H$ z4gFQkNUhjwx3^MbGYnEwLS!|rXsd&4Sjxw4)1XA;28<+8&DqeT5hiGLEs8Q4umQG8 zegItyt-I6=NRdbGq-HUN-r}%@ylKkFCM1lZP4?J+E@!USIC}ZC1eHzJQKdEk0UbOa>%y7XBBjukA&AKXNKh7JSavp+Vr|_4sBhWj`~MVM|z|;qJ96& zmbiw7@-r>W+kglN)TsBTkT6IwCus_uZ5b>+oZ99If5z4f1)uv?teba-6bri(SFS2j z(TJ?b>|Rx_wOdi#3_IQHZ_$#pzRh}0uJSNm@zs@U?Xc;!qw8oCgCPO!nB%zwh%mf* z(bl$Y>1ECr>$8DLX~H2ryr7X<50Jthj<(M;P~m+&2t__i4i(5;8)*qX9>LoWDr1}m zxk0EpUiacACI18I0FiLp~9i>oPf0P-phMBQz%YNWL>8k7Br!6xS>&@p9@}f1Q z0%5D^4?#^ap=UpO^r#~%Jp*;kN{zOU5Vjh&;9klL;O>M3+Na=O1j}i8eZ!B)Fw_El znz8>R*37`dsYhv4stmThiq1UVUE{sNqXzW8<_H#OyIVA&jw0a{kD|F4UZu4tc8wkW zCoYXxkS0wa$r@Kz6S2mkF9N|?f20W*vF4!ZT~p7X2xHfK)d%5*ax!r^y(y;^IYAgZ zo)c_P-lQXXZ?@=e1&$VibJe-*yrg)HU|(uQvW0{wt=#k48~!Isg(@{#L5!SA3&_rv zPkH?y$c!~t{N|dAy?#tbq@clgnNRVvyjK`>e~9`isD0UxL6Km!RsRc(C;u44vuz`b zXsa-x1S#+H9D&Go_t~}?TM489DT%1@jP2PHX(J5J&iYA41G`=qTNj?3OU&Es;mo#$ z(WS(~I7oXR@?BrN&+fC}LzBhjwTk*XXWKSsy|c7xJeJtxHwF4_iu;Ht5+nX#f5RTI zKzswU->Xm(zBis=PwXrP=FZdX+obI`Si@Cq8gS0E1BLE^R%7LZ=_jjaPjh4FlB}O1%lhYKSK~9rtU~-gAQd30X zoN>Xc;=HJG2Q9%Xd=5@C!)L@f-kUx0h<(Q)P$A*Iz#9?cxJN;(i5P@w^w4*1xDvPw zun<{#4^jUFlhmguatBvGq7Dsp+IR*QR9W`p+(-z%a?m?50KEbIX#VCg>p1=fiG}9w zC29Eo;%=IDm*K`@yRhUtBTN>-{m`%xUi*0)DJH=UbyS*X!2i%?O?`$@srfjJ>B^}! zeXR7xnqCyUH1rDqT!RkG<=0>V_wehevZ(nBYLWe^+bGg9&gUJEW(@PDc`}3Uv4>Z=xE8C+O^e9V&xVUS3s!@#mvf1&0%u?ZRBBrLOAOO^NPeqp$b(#vg>?yk9=o~u39}PGcsZTqo zh9mM)ZF1@hY7r8uuc#O}YTsM+A0~th8WqZ-S}q%96Ys&FRkkZ9K>QyPp>zQ^U*j>sTw+I7FxS4pDk(PCV{pyty`K*JM0t|c_rIxuO;V7 zj_Vy47~Fxrv=0n3FjERPFAT#25Bv%F8+g$xPkrXCPyBx8O4?m}?K*&V?v;-2)jj8) z?{oL&|SBrZrP^s zis2Q#(uamu+O=9mKxMB2sNxrQ3#}61s#gPC6Sxd`!m9(W3tRy_=`{d11g-); zz*B%9^S%Q3D*~Se{J7@;b_AaG%sWQ&iO>5j^XL{C$X3Hl3J~UcI&AC=d^Q=|&E1FxoW~+*4;S`>0 z{)FZdp6i-Ftx4}RD4R~~x5J*_Ax>VrTWGh?qA7Gc=8n-eb}aOu17Da6O)IgN-E}`H zY2urO#E$&lk}6@oDx>?Wp{lrj@%r3KxbDv_b{D(B+?_XHd**fD^?WtA+>Ph9eC2P2 zz5D(%i(xz$sm|Pn+gWp${W-23W#db83iL&@8{0{x-R=h6xZNg-h&pDNC9^-t4a{a^ z9rBhJ@0=q$Ig5uK{8o)sGsa$a&1=v{y_a*zxv;-!xSPAIKhbM^#ViuIIJ4 z!%fA_PT);MP8nwZv`{cS+K~|q@=jJfK#Q)SBMp1zj=5*;8=t~5JZrC@rHK)HwYX!> z7`H*+zWJ%~Sz+IN4RpSW%?P!{g}(Ko)3=^>@@*0Ug%+`05ClJ-`Tz)sX5V-RpWd^w zty&_T#16n=NYR>oHZj$KZGKU`w(JM~`y1-|DXvKbw$Z;LdyM?RYm-s!UN?$oul2%? z+l#Kx=ASf68bwuRc7vr*ah1eg4ZDF>kUHR|B^*u)onCYRf6^F5(#VJaqtA>XVuaP? zgb17hiz#w**V1A(lUg_G22tz=9c*4p_#CDk#9!`4vc=Owi0gtl9msi46)U0%|H=E< zh()l!&P=u96+R|@d*RSW@R{1eVhHLZkV=i%Pl+ukvb|MQZrs%zY{H6#@tvL>_thDZzJf|Ws>k!cC>x) zOxRJjU_BWd$rg&N-H$H?eBBpUe@MeQh=Az(TX3p2A>KYq!@! zZxcSGoz&aydz)@gZ$RXw=vWnvUQvq`xs=plwQ;wz?#C;kryl2fYDF?3dYZnbpHWSA zWcpA?u8DFpK1ookm<_v9;ID4iD|K5&I~dI`YTrCsw1RFBgI8^d!0^wlqF(?caTriZ zAjx9@l?9SK22e#H$zlLi1 zwRs@$nOD!Sd{ z^jJq9na}9;45p_>dco{JIGYXioC^M>uW?&V;pjW63k7x0JLVZ&h^RCXlmXhKdZWIP$ zZi#)zr_A3D7Z-7_-&Nt}V$WZ9*8ml7tIc3dJuhEJ-Sy3$8~eh`@yel?>TtH0n_)G~ zhB=Mj6k5aVpBv?4y50x}X@Dc@9-C`c$~`1-1MXydE6Co|w@G4gV+F482-04{47OCP zHM2jexAN_HTIk$F8ksa5k%dSzlU7ELt?Bs==_$Q|#1(v|@*P1Hhi;;!RDnt7N14RT ziyAqeG6$b(%1*+ir21MAh3i4r?Hof>QboF?XT36@4E!3UET1%zi5-erJCI+Ri3w+p zNx{VFzfP1Asm%V_5muyW*g~@|0Y!ZcGu7AG(ML*T5KUFzB1lbCl(#nhc|I|y3n4XcQY0X!t3Pby6PB)MV>s`+~9O8OmxaPR= zjl}NxOL0=tvy!6fF0aJ>nU|e0OLQZIl!>I!^M@)H;k!;rSbl0{n&XqinhCvdo68*& zUPKY^;wwqz4C|~pZB3h(tbQXGk+`3QX8E9QOG-3QaM9>-i4Gm$Tg1TZw_;WbTI&Gh zSFX5Bb~DA5w1meClH7O&D6FlnW5OYY6+NL@9nup^U4>-rz2dM|1XIwR(y;D`S{D`+ z6|n!pU8EYmh}H?hOvLy7=xHa?=}O@H9@CYzZm;LK&VAfi_dEd}{QxTrs!A=Rfp}Hrsg5w{L0yXV!y#4 za!3rf@G?5N-{2bRWpqbyg!ByzXl*)8!qilpP2H+54#(;vz67pBv!ieuLvMqG;Ui^`VN0!9yAPe|1% z(fJiFlAbo0ZelJDWf(QC%x~|bMU2_>IZs=c&3;4JdYrxwoW6+UNPVYL-sAe(^EYfI zYD#3;#of-MMT`dsi@`g*foFnTLH3j-rjitZj=pabz&} z?n-#ji~8?(R(RBM!?{WebmFbh2{zZEJW;b*lmms#aCkJHP+mP8}dT7Y^gusy1wnnJCcZvdTQ(WDm89YGhx_#3p0u5%-FWx6^h z`#j)-L><*MF-A-@^(cdO&3pV|MV6=|L7iGChC>=)XS@X`ul; z5t9JBq{8)M>V_D{9yio2EUa#``#w52cL@Y})_(+(>7{>5;*oRz;olGUn(wC6v(-96=N%_6M|#sD>u${~&V8<#${;7r96Z zZ}N*9okxoq^l9~GOGx;gW|5_7pTl?9{ zs0n17fb)yNX&xitve1mGMtIPry%z>us$4k0D%yn`abN zuvOzf_}g3Tr}!cVUB#@M^+MzF)YYj|Q&s#L_|@<$P1*eY2dE!W;Q#;t literal 0 HcmV?d00001 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) From b85c20b65a3fd5dea3dc1ac8aa03019e9d3eaf97 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 16:09:17 +0200 Subject: [PATCH 5/9] started generalizing player --- src/package/games/players.py | 9 +++++++++ src/package/games/werewolf/players.py | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/package/games/players.py diff --git a/src/package/games/players.py b/src/package/games/players.py new file mode 100644 index 0000000..4db6972 --- /dev/null +++ b/src/package/games/players.py @@ -0,0 +1,9 @@ +"""Has a single class: Player""" + +# discord imports +import discord + + +class Player: + """This (abstract) class is a template for player objects for games""" + pass diff --git a/src/package/games/werewolf/players.py b/src/package/games/werewolf/players.py index 77f1589..c07abd6 100644 --- a/src/package/games/werewolf/players.py +++ b/src/package/games/werewolf/players.py @@ -1,7 +1,9 @@ import discord +from .player import Player -class Player: + +class Werewolf_player(Player): @staticmethod async def make(member, game): From 13064a3af0e231dc38241a9e9f702dbfbd68cc3f Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 16:09:38 +0200 Subject: [PATCH 6/9] more refactoring --- .../__pycache__/developer.cpython-37.pyc | Bin 2695 -> 2736 bytes .../__pycache__/send_message.cpython-37.pyc | Bin 1068 -> 1068 bytes src/package/developer.py | 23 +++++----- .../games/__pycache__/game_cog.cpython-37.pyc | Bin 4787 -> 3939 bytes src/package/games/game_cog.py | 40 ++++++------------ .../werewolf/__pycache__/cog.cpython-37.pyc | Bin 1597 -> 2028 bytes src/package/games/werewolf/cog.py | 26 ++++++++++-- src/werewolf_bot.py | 2 +- 8 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/package/__pycache__/developer.cpython-37.pyc b/src/package/__pycache__/developer.cpython-37.pyc index 96d4ba4c2155bcf82fb38466472a322b1847f2b1..e2450da55a8698910161c1eae73e628e69caee7a 100644 GIT binary patch delta 755 zcmaJ-U279T6y3W!`;}yqZL-;#RIL)Q?n{f<50tLW^l9l>p%%3dlsC{VGks8@C0c$5v_hRCS78;bk?1h{7-*H&FfXgC zGquDS;bGx(rg6;E-AP*-`<){E$5Q)a z>B-dr`g3_Si6;FusbEnCf?1|Cy67OHJnljQ0R7 z@xiDNyy`jugG+6@2BG)8>NFJ7faSUTosS3$iXEC+FT*NSXW?E z{IWXG5M}%R246&_8Kt99IHtTUU;I3#^r+9GryN@^dg0eGT1VIrZ|%!ImRdkmORMXa p;S!G+;1TiJPMlkkn#~J*ORSYHxHnLTJLFpkhHy1cOiDg%{svoroiP9a delta 705 zcmZ9J&2G~`6ou#d-%i{(3HgDxq=?@LsX@>#pb`tjq6LA3U;)`gR%S??Cbl&Wt*9ck zQY&>!HHdZc28eEX1H=n>g?Is8fCYD)zydEm-8skS=z9FQa$0d8xvoR-dGjsb`E=&K zhmU8c%|oV7?HQp4HBZPfw4KG-qIP!9QHSPENIO5*kuJ;6faa+yeM~I<=Pc2}!YQ!A zz@r{5W=%y}dP$n))PBtN*f5G2KX~%0fBp;_ifCysuBBSSLYkT*mLz_UrH21*+>dyw zh5m>&6|Upz;+I-6^*$R8BTszQmc_bOJ={iPOejpq00xTWRGpC>LdiBcRsp?C(@`j9VK3JZsTJ*qjCTT>dVU-agRVagqOcQ$Z==*& z4w6ph^NI|V5sPhkO%(OAnCg1t0;-9DAj%5V;b7_CC9KCm*b}>IT*~sP63}@E%;T1U z{gmxp5_zp9zUx~TYuLhN*Bqvt)(~swSBwt;Rq@5#fwK5*)^A=$xoKxt$7L+leBMj= zEgakSK7IllMT4L%wyX`fE@G?eX6vhqg!p0IfK6fASD_?s+fVPLW;Ys*{E+fGR`6Ac zhV0e5eb(L0*5G%gdQW0Q967ZWuHr2JUQ$I^pedGG(ml;HEJOUX1NVXS6a)eOP`q|l PbN6x1Vt77sTCn*CUa+Lk diff --git a/src/package/__pycache__/send_message.cpython-37.pyc b/src/package/__pycache__/send_message.cpython-37.pyc index b5974d7a1ef0f77359e99f19442f302a2e81b191..96a6f4f6649e3cce1b629e7fca42bc635af13a73 100644 GIT binary patch delta 19 ZcmZ3(v4(@oiI-OY~cF0 zxfcAhX&C>YhskAQ@HuYv6b&~z24~z%Sl{fJeXC0)w*Q}bfpJ2U+zM5PX2A|_r1sY*=7=W9cbu3{EXWQWBUjdri zFYS)Y9qzs~It||79(q9K%jlQ*3U8uc<}JR8eub~`b@WZX!8g&j_%*(TewAP6H_)%~ zoBS5~b-vASqu<~=py{pBBi({1J1Ty>NLZAFN@>2N(BQMw?+()_k7)qn@?+uC=QVTp zl*osJ2Sna|e;`FYic>vk-}n1roF*gxI8@+NL?=Q{*Hhrmqc4LfQ4!HY7xi>M!mZvx zGiGBWFvccYmYccNv&Tk{bE6Nbo;~AvGGJBhtxzO zBE9LNo?PVbxYhe;#;gahJ$BNNcgDtw*e%zxc&-oYBO9Vfty7 z`}&ytk;r!exlzBAEv#KGD>C0xnWaZv!qzDef`Q2s$C<>+ny3!gaF7Me&L$;g+(G>xIV zt>MT`Qnpb~%4>M4QOPZgFQVE2IYBL4VcUUJjct{%-JW9mFVV{p?qY#?gz60lD}0;# z*%a9&h$K<|Ml`JjB>n{?H>OAy#1>GHfoikk7);I1mG>uR2W0TTzaU)wWN8z$$OO}m z^(h)XhrT(7ul5*?9)gK87pF@;tH1lnoPDLEwo`nfgU~d}L^#%5AY!LAA+@vC;Rgm? zn{VOO#&EZI=kB~kRK^~s2v#%c{bEP&GjWUv>!;bNkDM_2wcl=N!>LS&j7A58(oSJ) zIyjUq?Iv9r!&{QkRW{}5{eMa9duR-^$$aw`D^{ocYk>vcDdK6~Z(#t=!2LOsTQ!g7 zZ;%nmPKr+#x#A)b#1%#5$}j#GS^I5EUdP%qC%F=W6wvVj~{|GXvo6D~70L_8iJ@l-ph*CjX|M9oG@y|aauvw*($9W>k6)sdH z;{ib|ohVB(S?v72`V1~W-;Dr+Y@YV;+e~ibq>9#irD4-XTu z`!x)RzA2p-VKNk@i@YAEqF9?OQ7NyAI|tyWFH+J4gbkIDaAu`f!f>{y%o3rB+ayl* zs~;!4Ys@oI&0ewk9qEo$Av;WYvGg>2fpTt`zKu%4&XMNPJEDIm_MN$iNvka6|Nsw~DrCeBLjdy88b-`RhwCTJ*!knV~XqfBl z2JY(DWJT-AB)^l~)LSUGlgDUpp^P+f_G}X)1kH1DbQCmZ<*qj&a+Rm9qUZ|9q^j)o z8N`*YBUo{Bs==bn<+|L_x=a!?)fheNocu}G7?Uz=Q0IK?_mZ0xAeDC(Z3YiTF0(U H*0lZuzn{TX literal 4787 zcmcIoOK;rP73Omuni;(;JF$(pbQ-{(z#gSYfdqlu+L0UEKr#s+BZX&Cg5tfBIO34g zOO6#0la-C`40O{~8^BujclbASQEt1+w##nP?;LVo@>nU*W&~c|7wUDji|bZs_iXgpVP#P5 zRnb=w)vz|G_v)zEL_J&?GzUbGMSE4O zp}ofKHMG~o2HG3kUKiG`*51sXJU$E(=cc!pq{{Qtwlj#uD3lI5JtvieVd$mO*^iZT zKS=yo31=rhxRG$rfj5v&GL(L>ANY=c=tYqX9X}onyhtS91F+WCbMxM(J9t_513&Ge zQkzaNiV4kK83}hFlf*la-|I!E^#F_ew8Z3RqH-5cLWR_NI<#pBy=O{G+Wm^G?i-gi zVf+<371ndBR};3VJlA@4Q57}3S43T`;N1`nAaCY1+QIeXgY4V0I`Bg;NmvC+GM+ec z_D7MQ22_DA(nIOc=cO`kCNdojZ<9W5IYTAOS`cxieajhmK@^@ihh73TN&iTy>3RuN zvPxgNL6oFk{G1mLqSnKOY zRdsEwy)?$U&`*s=aE^zXJ{zq7ozczSc??xgObbRC#i_&Ec1~p40pMym$Z+$Ogly?p z#nD0AP)%%?n|}HYE1@XhpjzJGC0ss>%OqF@g{C+4O+DM1Vwsz?9L@*$2vA69{j%0K zlmkHK89;O6O*%4wa&}{-POdxm9MUiwTBvX!Cyp9LQ2^O3hNI}r^J>|tHqa_jsZG?+ zqS}CR+;Vb(?HXp4*j5SKl_|E@rY=X=!2lWb*^``6oVdRD7NY!;k?7OZa7t5=nh;4b}vHT|8DFT_Q zk(6sxT%dw1UR|PsJZi2Wi&AwIbS4S;VNJJ;x2>98v$@|#*Yrml-o=yr62)Q|n+slc z8N4de;$8}2R}g>3;XJ_z&V@U&93r3 z<;RYX^`rRML1sMp#A#R9Neg!+lg=$NF^<4_i=)xL3J{dSleWnLsc>EL%riEW+r0lR z(jVELW;FF{#wA3g#pCYqv;}`9qhh_kjSBPz-Jj}etJJ0OD=-c6%q2<;0y1w#d|n{3m4u9B;6;isR$d3eQ>Mm48G@)1gZLvVDa?SV5+(B& z=C|h9>>I+Kn1ZOh(2*<5zD3EM68j6YY&SrI=G!gMp!xP1Xwa-}iw&{KYw7*U*c2CD z=*UD{WR_wJD!Z6{NbCfPLE0a9OaQ#Sc$BivlF{CT%uHzk=`m|Qgn^_n+sJRSKR<~_ z&T$ZiP9&wEerRXnPONwEB_~KhIp(&38F~|og-r_wN27{;`5PRXk{##Xek#?mr$lj# zWR+kSCE*An41(5S_wb`c)GV2%FbsnBKjt`pL*d)<`s~l zAd=bIWQjufB)hf?{S0J8vOr;@L@Ky-!YiRV+f&7%OtLF9ootw!6TLI$*;LkEw)z#x zj#V*t6p5_<(0dAcJBqGTJWCP%@wVk)Pl|54s;;uMzojNoi`4Z-=^=7Aqo{jAxch+& z%d!19hPH`;MFI(&$j6+ox2ZYH985zjI`2;~z~oLd*7Yqs#a~m;E&$hDnD5Z+Zo8)5 zqNT{w6>%4Jg$hnq*Qxd{728zIcH+{|4rX2kmcpO@Gv_*9eSv5{EEBsRvI zs3FgsQd}ewJXxpNSIc|Sc~Vx($)C^3xV0c-IblJC1EM<8}I4a<^md?K<>KC zF@Av$)rVAkM8z_nAR7Mw&%`J6npray^)B}b2P5bE1dOVhHy%ZWD;&grcKDOIf*2`c zXOkShaHb|CeT66aFKnNv?K$V5NReODTBX%roP+lUqo0}-MUk1&_l=k4Dc$w7mmq!f zw?8(2T##B1h>alE_+TiaZO^QR%_XlCL(0Lr=g5p$oW{c^By?dqxB{)ui%r8_P`Ec5 z4h83>HuEtC9@D}^c@alpGm84!8yt^w)7$eEX)3RzCqo<%-!zJZ(kQPR9i$sYg5^yn zilu8ST9R>cRL{+w_<-*b9H!YWd8=h|<+VG-jpx_&e-u7;%WUdRvxWz6d+V+BYwNB5 E0M2EN=Kufz diff --git a/src/package/games/game_cog.py b/src/package/games/game_cog.py index b530cff..27c5e4a 100644 --- a/src/package/games/game_cog.py +++ b/src/package/games/game_cog.py @@ -1,25 +1,23 @@ -"""This (abstract) module is a template for Discord Cog's for game specific channel commands""" +"""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 from .game import Game -# TODO: take group as argument to add subcommands - -class Game_cog(Send_message, commands.Cog): +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""" - def __init__(self, bot, game_instances: Dict[discord.TextChannel, Game]): + def __init__(self, bot, game_cls: Type[Game]): self.bot = bot - self.game_instances = game_instances + 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: @@ -36,17 +34,13 @@ class Game_cog(Send_message, commands.Cog): 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): + async def setup(self, ctx): """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") + await self.send_wrong(ctx, f"A game '{self.game_cls.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}") + self.game_instances[ctx.channel] = self.game_cls(self.bot, ctx.channel) + await self.send_friendly(ctx, f"This channel can now play: {self.game_cls.name}") async def reset(self, ctx): """This function deletes the game instance for this channel""" @@ -54,10 +48,10 @@ class Setup_game_cog(Game_cog): del self.game_instances[ctx.channel] # TODO: better info message - async def info(self, ctx, game: Game): + async def info(self, ctx): """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_author(name=f"With this bot you can play {self.game_cls.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) @@ -66,10 +60,7 @@ class Setup_game_cog(Game_cog): 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): + async def pre_game_check(self, ctx): return self.setup_check(ctx) and self.not_running_check(ctx) async def players(self, ctx): @@ -79,10 +70,7 @@ class Pre_game_cog(Game_cog): 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): + async def in_game_check(self, ctx): return self.setup_check(ctx) and self.running_check(ctx) async def stop(self, ctx): diff --git a/src/package/games/werewolf/__pycache__/cog.cpython-37.pyc b/src/package/games/werewolf/__pycache__/cog.cpython-37.pyc index e3673ed4b1c9e330168b7b486239048bf91f7941..5ddac64b75f66da201e15a39a265b775da6eed03 100644 GIT binary patch delta 573 zcmZ8cJ!>055ZyWL-pPvYPL_p5Oc1BwAdVOpcEFtpiE#)9H7N|&9EWyK{y6K*-USmk zE+lXl5c>zDGU2S^TmJ~p>Z3?znb|k&!^_@Of4*do%5H-{a z-QL}hqI;a>LS|l+B;J|m<;Q7HaPK^czUXS4N-sYWUN<@uZQqc~D05=XS@o4Ru2!+q z)Zz^cgkIJTg%lTQ5{I3%yE*UdOiASR4e0mi8O6W>)f$&;Ssuw8Rd_P}+~Zyvdl&P6 z+S0*3oL$CC+B)dMEV!=`PT2Hy3*kaV^&b^e+Fw?G=;2G&ITD>ysb>eyxfG#ZEu0ng zF0!uH+16?mVG(Gd#%9NaHD-e1tkukozA*+MoS<}Q`-O=Wajw^umj1w0H>|O;iEyR{ YWDRlKUs8YB2AdS#r(H$O9i#T@3G7sZ{{R30 delta 215 zcmaFEzn4ediI;=lk4kl_Ht#SRnIjd_}xqu5e7QaD=}qSz;9 zd2@keL7+Hg;{JH9b_N!PDDGeeO`gpujP8t#Jd@Wj9cL7rT*7>SQEc)@7RkweEQ=VW zfh;*DO`gdfY~hoSvgz@GEazZkVG>~l!O5KL>M|(|%}fg!85vR-fTl6}X)@hnPfSUP oPtH#-VxJtyE+?!AvXlvAHDi$oko42!nB2s!GkG(+Ig@|{0N!jW#Q*>R diff --git a/src/package/games/werewolf/cog.py b/src/package/games/werewolf/cog.py index 87618b2..65b88ca 100644 --- a/src/package/games/werewolf/cog.py +++ b/src/package/games/werewolf/cog.py @@ -1,3 +1,5 @@ +"""Has a single class: Werewolf_Cog""" + # discord imports from discord.ext import commands @@ -6,12 +8,12 @@ from ..game_cog import Game_cog from .game import Werewolf_game -class Werewolf_cog(Game_cog): +class Werewolf_cog(Game_cog, commands.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? + # TODO: isn't there a better way to have a default subcommand? Maybe invoke super().info()? await ctx.invoke(self.bot.get_command('werewolf info')) @werewolf.command() @@ -29,6 +31,24 @@ class Werewolf_cog(Game_cog): """This function deletes the game instance for this channel""" await super().reset(ctx) + @werewolf.command() + @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() + @commands.check(Game_cog.pre_game_check) + async def start(self, ctx): + """starts a round of werewolf""" + await super().start(ctx) + + @werewolf.command() + @commands.check(Game_cog.in_game_check) + async def stop(self, ctx): + """aborts the current round of werewolf""" + await super().stop(ctx) + def setup(bot): - bot.add_cog(Werewolf_cog(bot, None)) + bot.add_cog(Werewolf_cog(bot, Werewolf_game)) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index c4b0ca0..9fb47b9 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -31,7 +31,7 @@ bot.load_extension('package.games.werewolf.cog') @bot.command() @commands.is_owner() async def reload(ctx, extension): - bot.reload_extension(f'{extension}') + bot.reload_extension(f'package.{extension}') # checker annotations From 54f8eaf4c2b42901d6484c4455ece9f13a1e708e Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 16:48:00 +0200 Subject: [PATCH 7/9] changed constant name to a function returning name --- src/package/games/game.py | 4 +++- src/package/games/werewolf/game.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/package/games/game.py b/src/package/games/game.py index 69d7c84..7a4ccf6 100644 --- a/src/package/games/game.py +++ b/src/package/games/game.py @@ -3,7 +3,9 @@ from abc import ABC class Game(ABC): - name = "Game" + @classmethod + def name(cls): + return "Game" def __init__(self, bot, channel): self.bot = bot diff --git a/src/package/games/werewolf/game.py b/src/package/games/werewolf/game.py index 4526027..4d1b9ee 100644 --- a/src/package/games/werewolf/game.py +++ b/src/package/games/werewolf/game.py @@ -8,7 +8,9 @@ from .players import Player, No_player class Werewolf_game: - name = "One Night Ultimate Werewolf" + @classmethod + def name(cls): + return "One Night Ultimate Werewolf" def __init__(self, bot, channel): self.running = False From e7b37217f9b083ca48dfb3d87335e0be50372851 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 16:49:19 +0200 Subject: [PATCH 8/9] changed name var to name func --- src/package/games/game_cog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/package/games/game_cog.py b/src/package/games/game_cog.py index 27c5e4a..7c9083e 100644 --- a/src/package/games/game_cog.py +++ b/src/package/games/game_cog.py @@ -37,10 +37,10 @@ class Game_cog(Send_message): async def setup(self, ctx): """This function creates an game instance for this channel""" if ctx.channel in self.game_instances: - await self.send_wrong(ctx, f"A game '{self.game_cls.name}' is already setup in this channel") + await self.send_wrong(ctx, f"A game '{self.game_cls.name()}' is already setup in this channel") else: self.game_instances[ctx.channel] = self.game_cls(self.bot, ctx.channel) - await self.send_friendly(ctx, f"This channel can now play: {self.game_cls.name}") + await self.send_friendly(ctx, f"This channel can now play: {self.game_cls.name()}") async def reset(self, ctx): """This function deletes the game instance for this channel""" @@ -51,7 +51,7 @@ class Game_cog(Send_message): async def info(self, ctx): """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 {self.game_cls.name}") + embed.set_author(name=f"With this bot you can play {self.game_cls.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) From 812ae8a9907f9c90d9f624e7ad423d52c4677a39 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 16:50:26 +0200 Subject: [PATCH 9/9] moved general player functions to super class --- src/package/games/players.py | 72 ++++++++++++++++++++++++- src/package/games/werewolf/players.py | 75 +++------------------------ 2 files changed, 78 insertions(+), 69 deletions(-) diff --git a/src/package/games/players.py b/src/package/games/players.py index 4db6972..7734f37 100644 --- a/src/package/games/players.py +++ b/src/package/games/players.py @@ -6,4 +6,74 @@ import discord class Player: """This (abstract) class is a template for player objects for games""" - pass + + @classmethod + async def make(cls, member, game): + p = cls() + p.member = member + p.dm = member.dm_channel or await member.create_dm() + p.game = game + return p + + def name(self): + return self.member.name + + def __str__(self): + return self.name() + + def reset(self): + pass + + def other_players(self): + return [p for p in self.game.player_list if p != self] + + async def send_normal(self, message): + await self.dm.send(message) + + async def send_embed(self, desc, color): + await self.dm.send(embed=discord.Embed(description=desc, color=color)) + + async def send_wrong(self, message): + await self.send_embed(message, 0xff8000) + + async def send_confirmation(self, message): + await self.send_embed(message, 0x00ff00) + + async def send_info(self, message): + await self.send_embed(message, 0x00ffff) + + # TODO: refactor this function to make it understandable + async def ask_choice(self, question, options): + text = f"{question}\n" + f"{'='*len(question)}\n\n" + '\n'.join(f"[{str(i)}]({str(options[i])})" for i in range(len(options))) + await self.dm.send(f"```md\n{text}```") + + # TODO: Basic Converters (https://discordpy.readthedocs.io/en/latest/ext/commands/commands.html#basic-converters) + def check_num(self, choice, N): + if not choice.isdigit(): + raise ValueError(f"Your choice {choice} is not a number") + if not 0 <= int(choice) < N: + raise ValueError(f"Your choice {choice} is not in range 0 - {N-1}") + + # TODO: seems hacky, figure out a nicer way + async def receive_choice(self, options, n_ans=1): + while True: + def check(choice): + return choice.channel == self.dm and choice.author == self.member + choice = (await self.game.bot.wait_for('message', check=check)).content.split() + + if not len(choice) == n_ans: + await self.send_wrong(f"Please give {n_ans} numbers not {len(choice)}") + continue + try: + for c in choice: + self.check_num(c, len(options)) + except ValueError as error: + await self.send_wrong(str(error)) + continue + + await self.send_confirmation(f"Received: {', '.join(choice)}") + return [int(c) for c in choice] + + async def get_choice(self, question, options): + await self.ask_choice(question, options) + return (await self.receive_choice(options))[0] diff --git a/src/package/games/werewolf/players.py b/src/package/games/werewolf/players.py index c07abd6..a17ab9c 100644 --- a/src/package/games/werewolf/players.py +++ b/src/package/games/werewolf/players.py @@ -1,86 +1,24 @@ import discord +"""Has a single class: Werewolf_player""" + +# local import from .player import Player class Werewolf_player(Player): + """This class is for simulating non-role-specific werewolf players""" - @staticmethod - async def make(member, game): - p = Player() - p.member = member - p.dm = member.dm_channel or await member.create_dm() - p.game = game - return p - - def setRole(self, role): + def set_role(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 - - def __str__(self): - return self.name() - def swap(self, player_B): self.day_role, player_B.day_role = player_B.day_role, self.day_role - def other(self): - return [p for p in self.game.player_list if p != self] - - async def send_normal(self, message): - await self.dm.send(message) - - async def send_embed(self, desc, color): - await self.dm.send(embed=discord.Embed(description=desc, color=color)) - - async def send_wrong(self, message): - await self.send_embed(message, 0xff8000) - - async def send_confirmation(self, message): - await self.send_embed(message, 0x00ff00) - - async def send_info(self, message): - await self.send_embed(message, 0x00ffff) - - async def ask_choice(self, question, options): - text = f"{question}\n" + f"{'='*len(question)}\n\n" + '\n'.join(f"[{str(i)}]({str(options[i])})" for i in range(len(options))) - await self.dm.send(f"```md\n{text}```") - - # TODO: Basic Converters (https://discordpy.readthedocs.io/en/latest/ext/commands/commands.html#basic-converters) - def check_num(self, choice, N): - if not choice.isdigit(): - raise ValueError(f"Your choice {choice} is not a number") - if not 0 <= int(choice) < N: - raise ValueError(f"Your choice {choice} is not in range 0 - {N-1}") - - async def receive_choice(self, options, n_ans=1): - while True: - def check(choice): - return choice.channel == self.dm and choice.author == self.member - choice = (await self.game.bot.wait_for('message', check=check)).content.split() - - if not len(choice) == n_ans: - await self.send_wrong(f"Please give {n_ans} numbers not {len(choice)}") - continue - try: - for c in choice: - self.check_num(c, len(options)) - except ValueError as error: - await self.send_wrong(str(error)) - continue - - await self.send_confirmation(f"Received: {', '.join(choice)}") - return [int(c) for c in choice] - - async def get_choice(self, question, options): - await self.ask_choice(question, options) - return (await self.receive_choice(options))[0] - async def get_double_choice(self, question, options): await self.ask_choice(question, options) return await self.receive_choice(options, 2) @@ -95,6 +33,7 @@ class Werewolf_player(Player): await self.send_confirmation("You are ready to vote") -class No_player(Player): +# TODO: this seems hacky, find another approach +class No_player(Werewolf_player): def name(self): return "no one"