From ff655eecdd217b0fe7d6b24f91c8dc4ae390d794 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 1 Apr 2020 00:50:17 +0200 Subject: [PATCH 01/26] small fixes --- src/werewolve-bot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/werewolve-bot.py b/src/werewolve-bot.py index 405b726..cab7b2d 100644 --- a/src/werewolve-bot.py +++ b/src/werewolve-bot.py @@ -1,6 +1,5 @@ import os import discord -from enum import Enum from random import shuffle import asyncio from dotenv import load_dotenv @@ -402,7 +401,7 @@ class one_night: for d in dead: if isinstance(d.day_role.copy, Tanner): - await self.send(str(p) + " won a tanner") + await self.send(str(d) + " won a tanner") await self.send(":skull: " + ', '.join(str(d) for d in dead)) await self.send('\n'.join(":ballot_box " + str(p.tally) + " votes for " + str(p) + " who is " + str(p.day_role) + " (was " + str(p.night_role) + ") " for p in self.player_list)) From 9b367001b472caf3549be55fcceaed752cafb19a Mon Sep 17 00:00:00 2001 From: bibin Date: Thu, 2 Apr 2020 20:47:11 +0200 Subject: [PATCH 02/26] organize in several files --- src/__pycache__/Role.cpython-37.pyc | Bin 0 -> 7033 bytes src/__pycache__/werewolf_game.cpython-37.pyc | Bin 0 -> 9546 bytes .../werewolf_players.cpython-37.pyc | Bin 0 -> 3113 bytes src/__pycache__/werewolf_roles.cpython-37.pyc | Bin 0 -> 7043 bytes src/werewolf_bot.py | 65 +++ src/werewolf_game.py | 218 ++++++++ src/werewolf_players.py | 56 ++ src/werewolf_roles.py | 156 ++++++ src/werewolve-bot-old.py | 381 -------------- src/werewolve-bot.py | 498 ------------------ 10 files changed, 495 insertions(+), 879 deletions(-) create mode 100644 src/__pycache__/Role.cpython-37.pyc create mode 100644 src/__pycache__/werewolf_game.cpython-37.pyc create mode 100644 src/__pycache__/werewolf_players.cpython-37.pyc create mode 100644 src/__pycache__/werewolf_roles.cpython-37.pyc create mode 100644 src/werewolf_bot.py create mode 100644 src/werewolf_game.py create mode 100644 src/werewolf_players.py create mode 100644 src/werewolf_roles.py delete mode 100644 src/werewolve-bot-old.py delete mode 100644 src/werewolve-bot.py diff --git a/src/__pycache__/Role.cpython-37.pyc b/src/__pycache__/Role.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d429ccc0be579eff70c83a561a2a0a7353c8592d GIT binary patch literal 7033 zcmb_gTW=f372eq!mlP?<@>PkGZXL%Blg9EbX^<(gE!- zZGv`X1$2dJ3$!PzpsP&Vplfml^bFGu=(=oxZZPeFo|SW;=a{a5o|h*;pJ3Vpy&zA5 zKFM?y^eK57^l7GRpwGy&pwBWr1Nxj?1ii>~9rSbZdC<=@-B3cA%2Kv+l&dPrQ&m+{ zGpfE`S4J=^&)+keFLXwFp{s?qj226D%2*N7STUuL=GVrGB`s;=3XMn?S4UQ)hpQ{A zvW9C#&d55ho@~fjTp>%&$@#C1U9ke0@`POA`-VIzPhoymo`(E0sr^~hR~-`q=|@*; zAEL!Dov|Z!jKJ8zKfr46hG;d-)Lw57RBCsk&26G$)nC)}+P@9$;PU;ArHyEymR7r~ z-EisNZ|=N)SGA?mOY7Za>56#+7I{kz4 z^a)J;1TC|yIjpiIdaqm%xejzsfSZJxDiuD{OfK#vu+2I;~zb~jK*QH2+j!1CJkEM3y`n7@R^M)8CudTId{2Iv>Rf^KpF-xzDi5y+e# zvIA$#73UkEd&R}V+|;|DMclQ0LeAI&h#;2K;up~+#x9_=Bld)VwR>jIdTeLZ2=h+r z#9NzKKbwXaq>VhBuLb>XoTP3WItb-t%moIkLBOT~Dhm@34|2Rb(AULqVYJZ8<*}FE z{f^%FC%S~zD0=3ukm7-(uYkgOrI|RR@w3LBxC4K^ji4Q_=JQrDmXFv6MqEm;va&vY z5~;6}KkG4X>MM)JkTfVs<7Q}PraBgf(PJ!2&l>^S8U!cV>l*qx;Mvjt>>i0)TA(JD{yhKm3;}TGt zXdEa#VViJO8tb5qe}v3sw3vKu(o?eflDq_-;iT>-s5hc;pIl%n@{N)95oV^4X6lPM z7R&-S;fwj0RgOMJhTYl+Gh_Jb=Ns@x27n(%BOm}Lew-P6Rdo=F0TVTiqNb~V1_lQV zsVN$0Qb~C{W(&QC=`6LtLZ{4OBis1p%rdQ}Q)(IxyX>$<$40mecE2Q+ltIY|nntEf zKq{r*af6lfNjru`i1AZJD(!>AM4YM(h%W~zeF0O`5h?>1?~*r2puCM%#HKl3*OLMh zaRw9+Mx{tlM)a(ny(@kYB+S;(Wqv+1Psz0~VrJdz7(2q8cWG$^as#O~TiB2DG5tE; zDYDO$XI;hAuh1qvi^itRNsFygL}Z(N%4rSX&En@(Kdbh$C>Uw~nxBVJieT|!!F1T5VCrAW zA0E!;FJ1Dpuv*WjnVK@zt5`YGIRd)Yui^d>b5Z?r4=qR1f*8(_N;1#8I#vp~%UCJ@ z0YRHjaQ5nP5xOfL82U}7?9muKv1s(1LT%x22U&mmy|-|jqw`x2XKa+RtJ3_v`IY#} z4A1kJg|Uay&c{6IWOppXPF!3oJ#R-W;uI;}v&MMDq>;iPA}fn4Els%qzC{;(e$-)r z?QBX^3A*Hr}>ShjbnQu_d(6solm5O#MRtr35DT2b* zeYAK1ogq$%3+6dd%l@WkP8#4^3qh4iz@kLUvQklY7NM^Q4>$0}r)XnW@oY_FPvt7^ zXVK~^oP}U8#s!s;6bso|NU3BsiY|rKd1UM?qw%qeAlCSqi{uV+psdi*>dhYjTeA_K z0P8N6Q@|RhbXxvFVABRjalCSpJ6)H9FTn^`a&FTMz?sVCdpjHE;Sl**R@TE3y@__IT?q3cHigaf3I41W&dREzx(}7~ z^)Dc#>FIZI(Ntkgpzi}BKctQ#*-_;gooKhwa^PJw%XWF=Nci!gxGYXBgYOr_dkjBE z)7VqN&$*?y!Uy|g&neuEz5J*!HOl1^Vagh1@}-HB(3*}0N1TNcK>wz`h$ZNksk?*@ zhwTFxdqVvBGhQm zuF{m*vTVp3xk==c;6vX+{(_coXC#>D zV+pRbBCZ0u|Fe!Kxdlo8nvhI1eaIy9@~1lf3c=@KO{XTV2RYYs#AWx<1P->6CXH!Y ty(UI_j=B;Pr`|i+7t;4gPE1Ag?921k{K9-~erEm*T4TOG?_z}i{|Bl`>D2%L literal 0 HcmV?d00001 diff --git a/src/__pycache__/werewolf_game.cpython-37.pyc b/src/__pycache__/werewolf_game.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8e5524dbb113048256b87b0c2f373a9358df880 GIT binary patch literal 9546 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 XFuiJAN_l*ljD);`*05Wz!?ye$t$LR@ literal 0 HcmV?d00001 diff --git a/src/__pycache__/werewolf_players.cpython-37.pyc b/src/__pycache__/werewolf_players.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bdcf3dfe787a37a9cda7d9bb1debebb8029c6663 GIT binary patch literal 3113 zcmb_eO>Y}T7@nD3+Z)Gml9o0t;v}%4WDu1&Sd`UfXX315@4B_6VE%l>yNZYY|Yos%=`7cA9H_csm}1bcIS`ZeyuR} z4=u`{i^&dJdIQ~eOftz|pp8X`Rzf;hIf{*)4wvpD)~bX!H~Bf3?VzQ7bc*2&>j>pY zF2xHr<{cMvN4j=ikri2e!8%o0lOD#JT#$8)o?Mg-j0Lglc=OSZ4{!gV0;%+7H_A2-l~#vI|CzeIn`E1*4$Cil z{?ICuZVaCrcMu#X(k8XAi7K!1t7u1yxyBo`KT(@xpu-R7!c}x5-eXV~wiq!VDg}?643WK325I+uwo_1IirU| z(Y+vA}SMlvKTCV5R(sX5x$wbeb$tgQ4ZEI<~nY{6QU#p?^eZr^Y zv0Ug>EEc{+8?)5Qx%IA!C3C%`&-hVemj0Z7ku6@)N$cUT!YNcc<9R6aiH8e~{6>DO zVhLFYYEv7iG!42JJxs4*^Bp2-eS3;I-JRo2P><>09dt+`NROx7VnXTB%S7g!0Ff~# zT+S{^<+cs4&jCdueopj0vGg9_I7XCYh}1D(1MM};Pq4ZUYHREl`1KPZDMdNw{MM0Z zt+!j;IBBM#AmMFzgb%f3m_1M2hjBck=&#FPq{z2 zCwb=5YRp|6Kj6vTtU~Y|Adm%&4MU6ezqrm?RZ}YpK<-wxFo`o2XQmdVGD1Q(P84VQ z13Jr1HMb@#WDPP+Gr6;e`zk#65JyXYD}m4|RM9i_8W{G1&V!l6VcR>1*;*D2RPq$) z<4dceKgN*9oP(NTyu%>M{Jli$k7(b?vz--)F65BSN@#^^&dYEECySihbdIhR=*^#* zAV_XSW`9MO*3nUf%*H0xGqGBxWa6Q3qkGMxrXFnSU6mDfxrL*JOe<55n(oiZL#fcp zL&*z8eomt6kZ`6hY4SSbK1(tb#Tj!^)X#{r5C&=H(E`9MPZ){>n_f$_rIFqvO48b?9%g- z^woE%qw=8XLhcKc&EAOim)O`uODTtXf>74I|KfC2>y6e!R)>#^_s2mTlP+L!7t<(gE!- zZGv`X1$2dJ3$!PzpsP&Vplfml^bFGu=(=oxZZPeFo|SW;=a{a5o|h*;pJ3Vpy&zA5 zKFM?y^eK57^l7GRpwGy&pwBWr1NsHI2zrs}I_PuqJm~XGHW$SXS&DUMG_n?Gh!S7k+)lk9=!V@S2uLHb zjm{9R7&b`O%cHq+p$U1J&?9k0>TdSi+e$Z0O{}y>9m$z>F7DE5tdh_9sS3!L^&y6{ zyx@4M1IY4^uGF5Ov3U7f%&;l4We%+1Zf>+=b(Q2Bv7ce~+4ytdeXUW>``UBheVxEi zBKP&@!28C@a^5$d1MfG_l=FUbj2?m*!GaF-aUk_%pGh^P)I#OZvd>MUI&I=i~2IPL-n)_M@=x8P&WG}X_#$C_KQgn&-851D*ko1o>{kp$?=aKUq<5qS%hhXXcLIA()#Ua zb5r%#+aWBzPfxd+Hg8m)1)*O+mpYL~L@X8QJmxQsBX=BD1gEcUML6LQ8LKm@U*7QcipF?Io^9kC|_tlcwv)?+)P zMwoX}C*InG{A?OxkT&wbz83Vmagw@i=pdAnF&7xD1_7G}s4PrCJjn6#KwlTbg^{3_ z%VRIS`-I;3C%S}W6g_iSNO9lMS3p5tX(rBS{H(DjK8C;ELC_9Y^LeWn%SY@3BQB*{ zSy>-Hi6mIbpY@nG^_4~Aadpp_T!Xb#ZE5nXVLji=0~NpH4;OzMZTW4j{A5GOu8 zHLs9O$UO~&E#F)a3*wwPoEuw{Q{A;@<$yEj7fBd~5;=I<$WhW!{r(6kWOBrrm+47% zTmotnjRU18Y!j|ZV;!{dkC3^H7L(6SdP-Jll9#|UoYegc^+pu#lM75mzA@51!ps!X zO#MoZ1+&0S_)sOjpT zfWZMnYKjJ$R8k&~*+TDQI!i6E&?$4+$TogCvrMb$l$wUaE<0?|u@NqV-7knGWl%DL zrjaQVkV@%y++gK=){bEjV*E^zO8ek25vOVc;>$rwU&PdOgvtQMyW|ZLDDR*Zv1yLw zdRky2&VT~Ks1ymxh@RE6cg6RDgxMOp%+H7BDY+I#%&dDIV@H_tQxc6pZXmU0g8eWb z({JFNBKu5v)>TaX5^d76Xl%-?wCFND2W)D{kRkoBkEdm9HkI?45L+D0k6D$U=S zkHjN0yuf1?#vVvJAM>P>-SHZB;^JEAc{^ee=Sk_FHO3<*jT8nESy^0ZY03rgExPCn zqYeXnPnmHvN%!9gzgcDNWs!|t;VpWLf?8S&DYfzWkON%i8&oqiEk10eqMeG>0?%2B zps;lhEnYxph*RRC`GTlrf73H34REc6ph_iRQKDs8sVF;(&{u?q8+hY$w6Uvrwx+RX zauxTBXmu6NLNFNPg33sWh3qV(RI(aHm%{2iGIo~H_}E1dYy8YbatAq3R_JK;=J$ZD z*$7X8br<3ku*NB!#6JjZ+5jnzH}EcQ5~?u)Yii`(xY~=xo|)C0j~xU~f}Po`lmHBy zsZ%Khp;wheZR4eGc78`a^RT^{1=F$cMOaNe+1_R(9pa}{z6{Xv4V~l94#kG99|;tC zc>;w6P$)^R5mz!QB}xE=#-0f%^)Gd_wc1yM_I<#Gj!NZl`2;i2$aB+wjVek%c^LVz+HC7~qWo}c08GS9cv+F=C~`8uQt?45ZXIF} z^1uWwFI!Mjis8x0ogay`FZk^YX$8s%S2^WOV9o{RsKVz|Elq?uqMQ=^SuLEERY!Fn zDCz5;W09t(-@`>yg*Ab`TSR_H9YwOE$}>9AZlmSEd&Ml<<&7iZ$A{vwIJFGEUlH#y z{2WbV&jdf`w%!Ww?~^^Ja5whyqr%iEmrsN#Ym~{CCQd?YIvN~t7D@p9oB9=qpkJl# z5;`2V4`A#m@#{}`sfb^7-qw+z`Uo$Olk_-%Ez&*eJCm;g<>h?lT^=bySqYHfOa+i| zGNQUlQ)bJuA#da+kxzpUeGB=5TT&Zyt#odbc!e-NwWpiRlxJuC~VLC>=%*kpL!*PKO{(T0119(0>1hHY%Ox z$C71chqlTOLWe4f5b;;yNI9AhLNqd^CQ5+IXor0^s{W?i?|11d;XZ}D?|(DO3X(GS zzJurgPOJ$%_+mu#vDS9KY=6 xCXH#5UK1ldM_q}DQ}5mE3+el`PE1Ag?5p$E{K9-~erEm*T4TOG?_z}i{|A1e?bHAO literal 0 HcmV?d00001 diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py new file mode 100644 index 0000000..c52ab07 --- /dev/null +++ b/src/werewolf_bot.py @@ -0,0 +1,65 @@ +import os +import discord +import asyncio +from dotenv import load_dotenv + +from werewolf_game import Game as Werewolf_Game + + +load_dotenv() +TOKEN = os.getenv('DISCORD_TOKEN') +if TOKEN is None: + print("Missing discord token!") + exit(1) + +bot = discord.Client() + +@bot.event +async def on_ready(): + print('We have logged in as {0.user}'.format(bot)) + + + +async def hello(message): + print("Hello") + await message.channel.send('Hello!:regional_indicator_a:') + + print(message.mentions) + +@bot.event +async def on_message(message): + + global running + + if message.author == bot.user: + return + + + if message.content.startswith('$hello'): + await hello(message) + return + + + if message.content.startswith('$logout'): + await bot.logout() + return + + + if message.content.startswith('$werewolf'): + + # start (only one instance running) + + if werewolf_game.running: + await message.channel.send("Sorry! A game is already running") + return + + werewolf_game.running = True + werewolf_game.set_channel(message.channel) + + + await werewolf_game.game() + + return + +werewolf_game = Werewolf_Game(bot) +bot.run(TOKEN) diff --git a/src/werewolf_game.py b/src/werewolf_game.py new file mode 100644 index 0000000..b01a931 --- /dev/null +++ b/src/werewolf_game.py @@ -0,0 +1,218 @@ +from random import shuffle +import asyncio +from werewolf_roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Villiager, Tanner, Hunter, No_role +from werewolf_players import Player, No_player + +class Game: + + def __init__(self, bot): + self.running = False + self.bot = bot + self.player_list = [] + self.role_list = Role.role_set + + + def set_channel(self, channel): + self.channel = channel + + + async def send(self, message): + await self.channel.send(message) + + + async def receive(self, command): + def check(msg): + return msg.channel == self.channel and msg.content.startswith(command) + + return await self.bot.wait_for('message', check = check) + + def setup(self): + self.werewolf_list = [] + self.mason_list = [] + + + async def set_players(self): + + await self.send("Who is playing?") + msg = await self.receive('$players') + + # use info from last round otherwise + if not msg.content.startswith('$players last'): + self.player_list = [await Player.make(mem, self) for mem in msg.mentions] + + # check conditions + if not 0 <= len(self.player_list) <= 10: + raise ValueError("Invalid number of players: " + str(len(self.player_list))) + + # send confirmation + await self.send("Players: " + ", ".join(p.name() for p in self.player_list)) + + + async def set_roles(self): + await self.send("With which roles do you want to play?") + msg = await self.receive('$roles') + + # use info from last round otherwise + if not msg.content.startswith('$roles last'): + tmp_role = [Role.match(r, self) for r in msg.content.split()[1:]] + + # invalid input + if None in tmp_role: + raise ValueError("Invalid list of roles: " + str(tmp_role)) + + self.role_list = tmp_role + + # check condition + if not len(self.role_list) == (len(self.player_list) + 3): + raise ValueError("Invalid number of roles: " + str(len(self.role_list)) + " with " + str(len(self.player_list)) + " players") + + # send confirmation + await self.send("Roles: " + ", ".join(r.name() for r in self.role_list)) + + + def distribute_roles(self): + shuffle(self.role_list) + for i in range(len(self.player_list)): + self.player_list[i].setRole(self.role_list[i]) + self.role_list[i].setPlayer(self.player_list[i]) + + self.middle_card = self.role_list[-3:] + self.active_role = sorted(self.role_list[:-3], key = lambda x: x.order) #necessary? + + + async def start_night(self): + await asyncio.gather( *[p.send("The night has begun") for p in self.player_list] ) + + async def send_role(self): + await asyncio.gather( *[p.send("Your role: " + p.night_role.name()) for p in self.player_list] ) + + async def night_phases(self): + await asyncio.gather( *[r.phase1() for r in self.active_role] ) + await asyncio.gather( *[r.phase2() for r in self.active_role] ) + await asyncio.gather( *[r.phase3() for r in self.active_role] ) + await asyncio.gather( *[r.phase4() for r in self.active_role] ) + await asyncio.gather( *[r.phase5() for r in self.active_role] ) + + async def start_day(self): + await self.send("The day has started") + + async def vote(self, options): + + # vote + await self.receive('$vote') + await self.send("Vote in DM") + + await asyncio.gather( *[p.cast_vote(options) for p in self.player_list] ) + + await self.send("Votes\n\n" + '\n'.join(str(p) + " :arrow_right: " + str(p.vote) for p in self.player_list)) + + def tally(self, options): + for o in options: + o.tally = 0 + + for p in self.player_list: + p.vote.tally += 1 + + def who_dead(self, options): + + maxi = max(o.tally for o in options) + dead = [p for p in self.player_list if p.tally >= maxi] + for d in dead: + d.dead = True + if isinstance(d.day_role.copy, Hunter): + dead.append(d.vote) + + return dead + + def who_won(self, dead): + + no_dead = (len(dead) == 0) + tanner_dead = any(isinstance(d.day_role.copy, Tanner) for d in dead) + werewolf_dead = any(isinstance(d.day_role.copy, Werewolf) for d in dead) + werewolf_in_game = any(isinstance(p.day_role.copy, Werewolf) for p in self.player_list) + minion_dead = any(isinstance(d.day_role.copy, Minion) for d in dead) + minion_in_game = any(isinstance(p.day_role.copy, Minion) for p in self.player_list) + + werewolf_won = False + village_won = False + tanner_won = False + + # could make it shorter using boolean algebra + if no_dead: + if werewolf_in_game: + werewolf_won = True + else: + village_won = True + else: + if tanner_dead: + tanner_won = True + + if werewolf_dead: + village_won = True + + else: + if werewolf_dead: + village_won = True + else: + if minion_dead: + if werewolf_in_game: + werewolf_won = True + else: + village_won = True + else: + if minion_in_game: + werewolf_won = True + + return werewolf_won, village_won, tanner_won, dead + + async def result(self, werewolf_won, village_won, tanner_won, dead): + + if werewolf_won: + await self.send("Werewolves won!") + + if village_won: + await self.send("Village won!") + + for d in dead: + if isinstance(d.day_role.copy, Tanner): + await self.send(str(d) + " won a tanner") + + await self.send(":skull: " + ', '.join(str(d) for d in dead)) + await self.send('\n'.join(":ballot_box " + str(p.tally) + " votes for " + str(p) + " who is " + str(p.day_role) + " (was " + str(p.night_role) + ") " for p in self.player_list)) + await self.send("Middle cards: " + ', '.join(str(r) for r in self.middle_card)) + + # debug + await self.send("Success") + + def end(self): + self.running = False + + + async def game(self): + + try: + + self.setup() + await self.set_players() + await self.set_roles() + self.distribute_roles() + await self.start_night() + await self.send_role() + + await self.night_phases() + + await self.start_day() + #discussion timer + + options = self.player_list + [No_player(self)] + await self.vote(options) + self.tally(options) + await self.result(*self.who_won(self.who_dead(options))) + + except ValueError as error: + await self.send(error) + except asyncio.TimeoutError: + await self.send("Error: I got bored waiting for your input") + finally: + self.end() + await self.send("Game ended") diff --git a/src/werewolf_players.py b/src/werewolf_players.py new file mode 100644 index 0000000..9086772 --- /dev/null +++ b/src/werewolf_players.py @@ -0,0 +1,56 @@ +class Player: + + @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 + + @staticmethod + def swap(player_A, player_B): + player_A.day_role, player_B.day_role = player_B.day_role, player_A.day_role + + def setRole(self, role): + self.night_role = role + self.day_role = role + + def name(self): + return self.member.name + + def __repr__(self): + return self.name() + + def other(self): + return [p for p in self.game.player_list if p != self] + + async def send(self, message): + await self.dm.send(message) + + async def ask_choice(self, options): + await self.send('\n'.join( "(" + str(i) + ") " + str(options[i]) for i in range(len(options)) )) + + async def receive_choice(self, options): + def check(choice): + return choice.channel == self.dm and choice.content.isdigit() and 0 <= int(choice.content) < len(options) + + return int((await self.game.bot.wait_for('message', timeout=30.0, check = check)).content) + + async def get_choice(self, options): + await self.ask_choice(options) + return await self.receive_choice(options) + + async def cast_vote(self, options): + self.vote = options[await self.get_choice(options)] + +class No_player(Player): + + def __init__(self): + self.day_role = No_role() + + def name(self): + return "no one" + + def __str__(self): + return self.name() diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py new file mode 100644 index 0000000..8d108cd --- /dev/null +++ b/src/werewolf_roles.py @@ -0,0 +1,156 @@ +class Role: + + def __init__(self, game): + self.game = game + self.copy = self + + def setPlayer(self, player): + self.player = player + + async def phase1(self): # query stuff + doppelganger simulation + pass + + async def phase2(self): # werewolf stuff + seer info + pass + + async def phase3(self): # robber simulation & info + pass + + async def phase4(self): # troublemaker simulation + pass + + async def phase5(self): # mostly sending info + drunk simulation + pass + + @staticmethod + def match(message, game): + for role_class in Role.role_set: + if message.casefold() == role_class.name(): + return role_class(game) + + @classmethod + def name(cls): + return cls.__name__.casefold() + + def __str__(self): + return self.name() + +class Doppelganger(Role): + order = 1 + +class Werewolf(Role): + order = 2 + + def setPlayer(self, player): + super().setPlayer(player) + self.game.werewolf_list.append(player) + + async def phase2(self): + if len(self.game.werewolf_list) >= 2: + await self.player.send("Werewolves: " + str(self.game.werewolf_list)) + else: + await self.player.send("You are the only werewolf") + await self.player.send("Which card in the middle do you want to look at?") + self.choice = await self.player.get_choice(["left", "middle", "right"]) + + await self.player.send("A card in the middle is: " + self.game.middle_card[self.choice].name()) + + +class Minion(Role): + order = 3 + + async def phase2(self): + if len(self.game.werewolf_list) == 0: + await self.player.send("There were no werewolves so you became one") + else: + await self.player.send("Werewolves: " + str(self.game.werewolf_list)) + + +class Mason(Role): + order = 4 + + def setPlayer(self, player): + super().setPlayer(player) + self.game.mason_list.append(player) + + async def phase2(self): + await self.player.send("Mason " + str(self.game.mason_list)) + + +class Seer(Role): + order = 5 + + async def phase1(self): + await self.player.send("Which 1 player card or 2 middle cards do you want to look at?") + self.choice = await self.player.get_choice(self.player.other() + ["left & middle", "middle & right", "left & right"]) + + async def phase2(self): + if self.choice < len(self.player.other()): + await self.player.send(self.player.other()[self.choice].night_role) + else: + self.choice -= len(self.player.other()) + if self.choice == 0: + a, b = 0, 1 + elif self.choice == 1: + a, b = 1, 2 + else: + a, b = 0, 2 + + await self.player.send(str(self.game.middle_card[a]) + " " + str(self.game.middle_card[b])) + + +class Robber(Role): + order = 6 + + async def phase1(self): + await self.player.send("Which player do you want to rob?") + self.choice = await self.player.get_choice(self.player.other()) + + async def phase3(self): + Player.swap(self.player, self.player.other()[self.choice]) + await self.player.send("You robbed: " + str(self.player.day_role)) + +class Troublemaker(Role): + order = 7 + + async def phase1(self): + await self.player.send("Who do you want to exchange? (send two separate numbers)") + self.A = await self.player.get_choice(self.player.other()) + self.B = await self.player.get_choice(self.player.other()) + + async def phase4(self): + Player.swap(self.player.other()[self.A], self.player.other()[self.B]) + # receive conformation + await self.player.send("Received " + str(self.A) + " " + str(self.B)) + +class Drunk(Role): + order = 8 + + async def phase1(self): + await self.player.send("Which card from the middle do you want to take?") + self.choice = await self.player.get_choice(["left", "middle", "right"]) + + async def phase5(self): + self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role + #receive conformation + await self.player.send("Received " + str(self.choice)) + +class Insomniac(Role): + order = 9 + + async def phase5(self): + await self.player.send("You are now: " + str(self.player.day_role)) + +class Villiager(Role): + order = 10 + +class Tanner(Role): + order = 11 + +class Hunter(Role): + order = 12 + +class No_role(Role): + order = 1000 + +Role.role_set = [Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Villiager, Tanner, Hunter] \ No newline at end of file diff --git a/src/werewolve-bot-old.py b/src/werewolve-bot-old.py deleted file mode 100644 index ba07ef9..0000000 --- a/src/werewolve-bot-old.py +++ /dev/null @@ -1,381 +0,0 @@ -import os -import discord -from enum import Enum -from random import shuffle -import asyncio -from dotenv import load_dotenv - -load_dotenv() -TOKEN = os.getenv('DISCORD_TOKEN') - -class Role(Enum): - doppelganger = 1 - werewolve = 2 - minion = 3 - mason = 4 - seer = 5 - robber = 6 - troublemaker = 7 - drunk = 8 - insomniac = 9 - villiager = 10 - tanner = 11 - hunter = 12 - -class Player: - - def __init__(self, member, role): - self.member = member - self.night_role = role - self.day_role = role - self.vote = -1 - self.tally = 0 - - async def create_dm(self): - self.dm = self.member.dm_channel or await self.member.create_dm() - - async def send(self, message): - await self.dm.send(message) - - async def sendRole(self): - await self.send("Your role: " + self.night_role.name) - - async def sendPoll(self): - await self.send(Player.poll_message) - - async def sendMiddle(self): - await self.send(Player.poll_middle) - - async def receiveChoice(self): - - global bot - - def check(vote): - - return vote.channel == self.dm and vote.content.isdigit() and 0 <= int(vote.content) < Player.size - - vote = await bot.wait_for('message', timeout=30.0, check = check) - await self.send("Received: " + vote.content) - - return int(vote.content) - - -bot = discord.Client() - -@bot.event -async def on_ready(): - print('We have logged in as {0.user}'.format(bot)) - - - -running = False - -async def hello(message): - print("Hello") - await message.channel.send('Hello!:regional_indicator_a:') - - def check(vote): - return vote.content in ["a", "b"] and vote.channel == message.channel - - vote = await bot.wait_for('message', timeout=10.0, check = check) - await message.channel.send("Received! " + vote.content) - -@bot.event -async def on_message(message): - - global running - - if message.author == bot.user: - return - - - if message.content.startswith('$hello'): - await hello(message) - return - - - if message.content.startswith('$logout'): - await bot.logout() - return - - if message.content.startswith('$werewolve'): - - # start (only one instance running) - - if running: - await message.channel.send("Sorry! A game is already running") - return - - - - - """ - if len(players) < 4: - await message.channel.send("To few players!") - return - - """ - - running = True - - - # setup - - members = [mem for mem in message.channel.members if not mem.bot] - - Player.size = len(members) - - - role_set = [Role.werewolve, Role.werewolve, Role.drunk, Role.insomniac, Role.seer, Role.robber, Role.hunter] + (Player.size-4)*[Role.villiager] - shuffle(role_set) - - players = [] - - - werewolve = [] - minion = None - mason = [] - seer = None - robber = None - troublemaker = None - drunk = None - insomniac = None - villiager = [] - tanner = None - hunter = None - - for i in range(Player.size): - - players.append(Player(members[i], role_set[i])) - await players[i].create_dm() - - - if role_set[i] == Role.werewolve: - werewolve.append(players[i]) - - elif role_set[i] == Role.mason: - mason.append(players[i]) - - elif role_set[i] == Role.seer: - seer = players[i] - - elif role_set[i] == Role.robber: - robber = players[i] - - elif role_set[i] == Role.troublemaker: - troublemaker = players[i] - - elif role_set[i] == Role.drunk: - drunk = players[i] - - elif role_set[i] == Role.insomniac: - insomniac = players[i] - - elif role_set[i] == Role.villiager: - villiager.append(players[i]) - - elif role_set[i] == Role.tanner: - tanner = players[i] - - elif role_set[i] == Role.hunter: - hunter = players[i] - - middle = role_set[-3:] - - - Player.poll_middle = "(0) left\n(1) middle\n(2) right" - - Player.poll_message = "" - for i in range(Player.size): - Player.poll_message += "(" + str(i) + ") " + players[i].member.name + "\n" - - # doing phase - - - #send role info to all - send_roles = [p.sendRole() for p in players] - await asyncio.gather(*send_roles) - - - - # query stuff - - #doppelganger stuff - - async def query_seer(): - if seer is None: - return - - await seer.send("Who do you want to look at?") - await seer.sendPoll() - - async def query_robber(): - if robber is None: - return - - await robber.send("Who do you want to rob?") - await robber.sendPoll() - - async def query_troublemaker(): - if troublemaker is None: - return - - await troublemaker.send("Who do you want to exchange?") - await troublemaker.sendPoll() - await troublemaker.sendPoll() - - async def query_drunk(): - if drunk is None: - return - - await drunk.send("Which card from the middle do you want to take?") - await drunk.sendMiddle() - - await asyncio.gather(query_seer(), query_robber(), query_troublemaker(), query_drunk()) - - - - #receive and confirm! - async def receive_seer(): - if seer is not None: - return await seer.receiveChoice() - - async def receive_robber(): - if robber is not None: - return await robber.receiveChoice() - - async def receive_troublemaker(): - if troublemaker is not None: - return await troublemaker.receiveChoice() - - async def receive_drunk(): - if drunk is not None: - return await drunk.receiveChoice() - - seerChoice, robberChoice, troublemakerChoice, drunkChoice = await asyncio.gather(receive_seer(), receive_robber(), receive_troublemaker(), receive_drunk()) - - - - # simulate - - #exchange robber - if robber is not None: - robber.day_role, players[robberChoice].day_role = players[robberChoice].day_role, robber.day_role - #exchange troublemaker - if troublemaker is not None: - A = players[troublemakerChoice[0]] - B = players[troublemakerChoice[1]] - A.day_role, B.day_role = B.day_role, A.day_role - #exchange drunk - if drunk is not None: - drunk.day_role, middle[drunkChoice] = middle[drunkChoice], drunk.day_role - - - - #send werewolves identity to werewolves and minion - async def send_werewolves(): - message = "" - for w in werewolve: - message += w.member.name + " " - - message += "were werewolves" - - sender = [bad.send(message) for bad in werewolve] - if minion is not None: - sender.append(minion.send(message)) - - await asyncio.gather(*sender) - - #send mason identity to masons - async def send_masons(): - message = "" - for m in mason: - message += m.member.name + " " - - message += " were masons" - - sender = [m.send(message) for m in mason] - await asyncio.gather(*sender) - - #send info to seer - async def send_seer(): - if seer is not None: - await seer.send(players[seerChoice].member.name + " was a " + players[seerChoice].night_role.name) - - #send info to robber - async def send_robber(): - if robber is not None: - await robber.send("You stole the role: " + players[robberChoice].night_role.name) - - #send insomniac new role to insomniac - async def send_insomniac(): - if insomniac is not None: - await insomniac.send("You are now a " + insomniac.day_role.name) - - await asyncio.gather(send_werewolves(), send_masons(), send_seer(), send_robber(), send_insomniac()) - - - - # discussion - - # vote - - def check_vote(vote): - return vote.content == "$vote" and vote.channel == message.channel - - await bot.wait_for('message', check = check_vote) - - await message.channel.send("Vote in DM") - - - send_votes = [p.sendPoll() for p in players] - await asyncio.gather(*send_votes) - - receive_votes = [p.receiveChoice() for p in players] - tmp = await asyncio.gather(*receive_votes) - for i in range(Player.size): - players[i].vote = tmp[i] - - for p in players: - players[p.vote].tally += 1 - - maxi = max( [p.tally for p in players] ) - - dead = [p for p in players if p.tally >= maxi] - - if hunter in dead: - dead.append(players[hunter.vote]) - - - - # result and end - # show day-role & night role - - msg = "" - for d in dead: - msg += d.member.name + " " - msg += "are dead!" - await message.channel.send(msg) - - if any(d in werewolve for d in dead): - msg = "The village won!" - else: - msg = "The werewolves won!" - - await message.channel.send(msg) - - msg = "" - for p in players: - msg += p.member.name + ": " + p.day_role.name + " (day) " + p.night_role.name + " (night) and voted for " + players[p.vote].member.name + "\n" - - msg += middle - - await message.channel.send(msg) - - - - running = False - - - -bot.run(TOKEN) \ No newline at end of file diff --git a/src/werewolve-bot.py b/src/werewolve-bot.py deleted file mode 100644 index 55bcafd..0000000 --- a/src/werewolve-bot.py +++ /dev/null @@ -1,498 +0,0 @@ -import os -import discord -from random import shuffle -import asyncio -from dotenv import load_dotenv - -load_dotenv() -TOKEN = os.getenv('DISCORD_TOKEN') -if TOKEN is None: - print("Missing discord token!") - exit(1) - - - -class Role: - - def __init__(self, game): - self.game = game - self.copy = self - - def setPlayer(self, player): - self.player = player - - async def phase1(self): # query stuff + doppelganger simulation - pass - - async def phase2(self): # werewolf stuff + seer info - pass - - async def phase3(self): # robber simulation & info - pass - - async def phase4(self): # troublemaker simulation - pass - - async def phase5(self): # mostly sending info + drunk simulation - pass - - @staticmethod - def match(message, game): - for role_class in Role.role_set: - if message.casefold() == role_class.name(): - return role_class(game) - - @classmethod - def name(cls): - return cls.__name__.casefold() - - def __str__(self): - return self.name() - -class Doppelganger(Role): - order = 1 - -class Werewolf(Role): - order = 2 - - def setPlayer(self, player): - super().setPlayer(player) - self.game.werewolf_list.append(player) - - async def phase2(self): - if len(self.game.werewolf_list) >= 2: - await self.player.send("Werewolves: " + str(self.game.werewolf_list)) - else: - await self.player.send("You are the only werewolf") - await self.player.send("Which card in the middle do you want to look at?") - self.choice = await self.player.get_choice(["left", "middle", "right"]) - - await self.player.send("A card in the middle is: " + self.game.middle_card[self.choice].name()) - - -class Minion(Role): - order = 3 - - async def phase2(self): - if len(self.game.werewolf_list) == 0: - await self.player.send("There were no werewolves so you became one") - else: - await self.player.send("Werewolves: " + str(self.game.werewolf_list)) - - -class Mason(Role): - order = 4 - - def setPlayer(self, player): - super().setPlayer(player) - self.game.mason_list.append(player) - - async def phase2(self): - await self.player.send("Mason " + str(self.game.mason_list)) - - -class Seer(Role): - order = 5 - - async def phase1(self): - await self.player.send("Which 1 player card or 2 middle cards do you want to look at?") - self.choice = await self.player.get_choice(self.player.other() + ["left & middle", "middle & right", "left & right"]) - - async def phase2(self): - if self.choice < len(self.player.other()): - await self.player.send(self.player.other()[self.choice].night_role) - else: - self.choice -= len(self.player.other()) - if self.choice == 0: - a, b = 0, 1 - elif self.choice == 1: - a, b = 1, 2 - else: - a, b = 0, 2 - - await self.player.send(str(self.game.middle_card[a]) + " " + str(self.game.middle_card[b])) - - -class Robber(Role): - order = 6 - - async def phase1(self): - await self.player.send("Which player do you want to rob?") - self.choice = await self.player.get_choice(self.player.other()) - - async def phase3(self): - Player.swap(self.player, self.player.other()[self.choice]) - await self.player.send("You robbed: " + str(self.player.day_role)) - -class Troublemaker(Role): - order = 7 - - async def phase1(self): - await self.player.send("Who do you want to exchange? (send two separate numbers)") - self.A = await self.player.get_choice(self.player.other()) - self.B = await self.player.get_choice(self.player.other()) - - async def phase4(self): - Player.swap(self.player.other()[self.A], self.player.other()[self.B]) - # receive conformation - await self.player.send("Received " + str(self.A) + " " + str(self.B)) - -class Drunk(Role): - order = 8 - - async def phase1(self): - await self.player.send("Which card from the middle do you want to take?") - self.choice = await self.player.get_choice(["left", "middle", "right"]) - - async def phase5(self): - self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role - #receive conformation - await self.player.send("Received " + str(self.choice)) - -class Insomniac(Role): - order = 9 - - async def phase5(self): - await self.player.send("You are now: " + str(self.player.day_role)) - -class Villiager(Role): - order = 10 - -class Tanner(Role): - order = 11 - -class Hunter(Role): - order = 12 - -class No_role(Role): - order = 1000 - - -Role.role_set = [Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Villiager, Tanner, Hunter] - - -class Player: - - @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 - - @staticmethod - def swap(player_A, player_B): - player_A.day_role, player_B.day_role = player_B.day_role, player_A.day_role - - def setRole(self, role): - self.night_role = role - self.day_role = role - - def name(self): - return self.member.name - - def __repr__(self): - return self.name() - - def other(self): - return [p for p in self.game.player_list if p != self] - - async def send(self, message): - await self.dm.send(message) - - async def ask_choice(self, options): - await self.send('\n'.join( "(" + str(i) + ") " + str(options[i]) for i in range(len(options)) )) - - async def receive_choice(self, options): - def check(choice): - return choice.channel == self.dm and choice.content.isdigit() and 0 <= int(choice.content) < len(options) - - return int((await self.game.bot.wait_for('message', timeout=30.0, check = check)).content) - - async def get_choice(self, options): - await self.ask_choice(options) - return await self.receive_choice(options) - - async def cast_vote(self, options): - self.vote = options[await self.get_choice(options)] - -class No_player(Player): - - def __init__(self): - self.day_role = No_role() - - def name(self): - return "no one" - - def __str__(self): - return self.name() - - -class one_night: - - def __init__(self, bot): - self.running = False - self.bot = bot - self.player_list = [] - self.role_list = Role.role_set - - - def set_channel(self, channel): - self.channel = channel - - - async def send(self, message): - await self.channel.send(message) - - - async def receive(self, command): - def check(msg): - return msg.channel == self.channel and msg.content.startswith(command) - - return await bot.wait_for('message', check = check) - - def setup(self): - self.werewolf_list = [] - self.mason_list = [] - - - async def set_players(self): - - await self.send("Who is playing?") - msg = await self.receive('$players') - - # use info from last round otherwise - if not msg.content.startswith('$players last'): - self.player_list = [await Player.make(mem, self) for mem in msg.mentions] - - # check conditions - if not 0 <= len(self.player_list) <= 10: - raise ValueError("Invalid number of players: " + str(len(self.player_list))) - - # send confirmation - await self.send("Players: " + ", ".join(p.name() for p in self.player_list)) - - - async def set_roles(self): - await self.send("With which roles do you want to play?") - msg = await self.receive('$roles') - - # use info from last round otherwise - if not msg.content.startswith('$roles last'): - tmp_role = [Role.match(r, self) for r in msg.content.split()[1:]] - - # invalid input - if None in tmp_role: - raise ValueError("Invalid list of roles: " + str(tmp_role)) - - self.role_list = tmp_role - - # check condition - if not len(self.role_list) == (len(self.player_list) + 3): - raise ValueError("Invalid number of roles: " + str(len(self.role_list)) + " with " + str(len(self.player_list)) + " players") - - # send confirmation - await self.send("Roles: " + ", ".join(r.name() for r in self.role_list)) - - - def distribute_roles(self): - shuffle(self.role_list) - for i in range(len(self.player_list)): - self.player_list[i].setRole(self.role_list[i]) - self.role_list[i].setPlayer(self.player_list[i]) - - self.middle_card = self.role_list[-3:] - self.active_role = sorted(self.role_list[:-3], key = lambda x: x.order) #necessary? - - - async def start_night(self): - await asyncio.gather( *[p.send("The night has begun") for p in self.player_list] ) - - async def send_role(self): - await asyncio.gather( *[p.send("Your role: " + p.night_role.name()) for p in self.player_list] ) - - async def night_phases(self): - await asyncio.gather( *[r.phase1() for r in self.active_role] ) - await asyncio.gather( *[r.phase2() for r in self.active_role] ) - await asyncio.gather( *[r.phase3() for r in self.active_role] ) - await asyncio.gather( *[r.phase4() for r in self.active_role] ) - await asyncio.gather( *[r.phase5() for r in self.active_role] ) - - async def start_day(self): - await self.send("The day has started") - - async def vote(self, options): - - # vote - await self.receive('$vote') - await self.send("Vote in DM") - - await asyncio.gather( *[p.cast_vote(options) for p in self.player_list] ) - - await self.send("Votes\n\n" + '\n'.join(str(p) + " :arrow_right: " + str(p.vote) for p in self.player_list)) - - def tally(self, options): - for o in options: - o.tally = 0 - - for p in self.player_list: - p.vote.tally += 1 - - def who_dead(self, options): - - maxi = max(o.tally for o in options) - dead = [p for p in self.player_list if p.tally >= maxi] - for d in dead: - d.dead = True - if isinstance(d.day_role.copy, Hunter): - dead.append(d.vote) - - return dead - - def who_won(self, dead): - - no_dead = (len(dead) == 0) - tanner_dead = any(isinstance(d.day_role.copy, Tanner) for d in dead) - werewolf_dead = any(isinstance(d.day_role.copy, Werewolf) for d in dead) - werewolf_in_game = any(isinstance(p.day_role.copy, Werewolf) for p in self.player_list) - minion_dead = any(isinstance(d.day_role.copy, Minion) for d in dead) - minion_in_game = any(isinstance(p.day_role.copy, Minion) for p in self.player_list) - - werewolf_won = False - village_won = False - tanner_won = False - - # could make it shorter using boolean algebra - if no_dead: - if werewolf_in_game: - werewolf_won = True - else: - village_won = True - else: - if tanner_dead: - tanner_won = True - - if werewolf_dead: - village_won = True - - else: - if werewolf_dead: - village_won = True - else: - if minion_dead: - if werewolf_in_game: - werewolf_won = True - else: - village_won = True - else: - if minion_in_game: - werewolf_won = True - - return werewolf_won, village_won, tanner_won, dead - - async def result(self, werewolf_won, village_won, tanner_won, dead): - - if werewolf_won: - await self.send("Werewolves won!") - - if village_won: - await self.send("Village won!") - - for d in dead: - if isinstance(d.day_role.copy, Tanner): - await self.send(str(d) + " won a tanner") - - await self.send(":skull: " + ', '.join(str(d) for d in dead)) - await self.send('\n'.join(":ballot_box " + str(p.tally) + " votes for " + str(p) + " who is " + str(p.day_role) + " (was " + str(p.night_role) + ") " for p in self.player_list)) - await self.send("Middle cards: " + ', '.join(str(r) for r in self.middle_card)) - - # debug - await self.send("Success") - - def end(self): - self.running = False - - - async def game(self): - - try: - - self.setup() - await self.set_players() - await self.set_roles() - self.distribute_roles() - await self.start_night() - await self.send_role() - - await self.night_phases() - - await self.start_day() - #discussion timer - - options = self.player_list + [No_role(self)] - await self.vote(options) - self.tally(options) - await self.result(*self.who_won(self.who_dead(options))) - - except ValueError as error: - await self.send(error) - except asyncio.TimeoutError: - await self.send("Error: I got bored waiting for your input") - finally: - self.end() - await self.send("Game ended") - - - -bot = discord.Client() - -@bot.event -async def on_ready(): - print('We have logged in as {0.user}'.format(bot)) - - - -async def hello(message): - print("Hello") - await message.channel.send('Hello!:regional_indicator_a:') - - print(message.mentions) - -@bot.event -async def on_message(message): - - global running - - if message.author == bot.user: - return - - - if message.content.startswith('$hello'): - await hello(message) - return - - - if message.content.startswith('$logout'): - await bot.logout() - return - - - if message.content.startswith('$werewolf'): - - # start (only one instance running) - - if werewolf_game.running: - await message.channel.send("Sorry! A game is already running") - return - - werewolf_game.running = True - werewolf_game.set_channel(message.channel) - - - await werewolf_game.game() - - return - -werewolf_game = one_night(bot) -bot.run(TOKEN) From eaf1686c5d8c0c0d94c421476bf80238301a059e Mon Sep 17 00:00:00 2001 From: bibin Date: Fri, 3 Apr 2020 16:09:39 +0200 Subject: [PATCH 03/26] on_message -> commands --- src/__pycache__/werewolf_game.cpython-37.pyc | Bin 9546 -> 9378 bytes .../werewolf_players.cpython-37.pyc | Bin 3113 -> 3151 bytes src/__pycache__/werewolf_roles.cpython-37.pyc | Bin 7043 -> 7026 bytes src/werewolf_bot.py | 83 +++++++++++------- src/werewolf_game.py | 33 +++---- src/werewolf_players.py | 15 ++-- src/werewolf_roles.py | 28 ++++-- 7 files changed, 93 insertions(+), 66 deletions(-) diff --git a/src/__pycache__/werewolf_game.cpython-37.pyc b/src/__pycache__/werewolf_game.cpython-37.pyc index e8e5524dbb113048256b87b0c2f373a9358df880..dd64ba7b90bde899458325189f4f61fd05dd2b02 100644 GIT binary patch delta 2918 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 delta 1238 zcmaJ=&rj4q6z;U$?QEAt6ax#92&*U?m8F2eL;{K$Q43l?m?z<&-%;4}0C>t~NW2 zQhOswZJV$q16^zt{-L(SY{?ToRSckCASWB=O5c8c{9rqcCo^@F+LE#zOY4 z#%BgaPj-Uzil^Cu(g1ouhY&NB)0n0cuiI`l@?CaWe9C@b`U@6HIV%mf>VzQ+cm{OP zqA+1$A`L(G{53z3nja>7D0L^Zbq8g&?zq|TdRcCjx$9TNzG0D~I5dj$Wu#^Jb@;2l z6zx@%NV@6xiM<+eK8)vD{o>M8kxn3jRHaHZ=-~rMa#KPoY-h}Q@FXsEgU1>fzXA3FNj}2=t00s;*E4ThTvle&16yu zd&3C;t{ACQ?Vuex)v4C_WvQOpw9cTIG+$Swu@KqmAf&U=_LbuU_Cx5YZHb@e{NVBB zH?OCe2i7ntto+>7Gv>jt?KpvPm=d4!Lt7S5_&CA@!W2T&+^ eIqoe$^RWr3(vB(V^hV^p3D`{9E1d35GWrjhzULPJ diff --git a/src/__pycache__/werewolf_roles.cpython-37.pyc b/src/__pycache__/werewolf_roles.cpython-37.pyc index 121e3a5e7ce5412c255a315b04595c1e2ec0abc7..58720c5315eacfb416e507c7b96b14b6859d293c 100644 GIT binary patch delta 626 zcmYjNJ4ho@6rDT1OtO=V`z9LWB!#$$yM7Q|P?IeFA|a?7al;1IQm~Ljh&%-gtqc}v z6ue*|_*+=4ccgQZFIodfugBQ!N5n!!s;E!lmB&b4kq;1>zh2p0n;~XHC9Z_>uyYWRRxUAg(Rzam}j) z-nMpkHd56Hxv43JF@`L|7ls~`!m*V_=WW_=^L)a5?0am2hz%wUf)EHj6i^cf_9Q;U zu3#7cwVj5?N%0vOs+ov7Zp9a3({$RQ{eD}hX}phLz&K|%S`YA|z0i`Qwta5p*We(P z!y97|N1bnWo~T3CbWc-xPjQ`;JwwEiPnq$Q{zS^2Blg6n%z4V6MA|M8``f1!@G;Tb p!9S{aUd59rj#TWf*hm#Hl^jfVOCh7ulwsK>BeGdqGDwW~y#im;FAAHj%VGrV?Z;rkC;=MsJ=Hs5+oO5&T`3_54=dDHCwiJ5&;s>je zfC4The!`jXoH+t0;;$n;m*vsx5Gpj_NAWmyV;!!ckLRBrCbVz`JFMVUghqUUsL2P3^rzbf1iTq*mYZ(Dos84Z(NhrUB|;8 z?luhy&BL9X4li*pcXeW!24C6Z`Iw^&! diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index c52ab07..c61c82e 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -1,8 +1,9 @@ import os -import discord -import asyncio from dotenv import load_dotenv - +import functools +import asyncio +import discord +from discord.ext import commands from werewolf_game import Game as Werewolf_Game @@ -12,54 +13,74 @@ if TOKEN is None: print("Missing discord token!") exit(1) -bot = discord.Client() +bot = commands.Bot(command_prefix=commands.when_mentioned_or('$werewolf ')) + @bot.event async def on_ready(): + await bot.change_presence(status=discord.Status.idle, activity=discord.Game('One Night Ultimate Werewolf')) print('We have logged in as {0.user}'.format(bot)) - -async def hello(message): - print("Hello") - await message.channel.send('Hello!:regional_indicator_a:') - - print(message.mentions) - @bot.event async def on_message(message): - - global running - if message.author == bot.user: return + await bot.process_commands(message) - if message.content.startswith('$hello'): - await hello(message) +@bot.group(help="werewolf game", description="Prefix for the One Night Ultimate Werewolf commands.") +async def game(ctx): + if ctx.invoked_subcommand is None: + await bot.say('Invalid sub command passed...') + + +@game.command(help="start werewolf game", description="Start One Night Ultimate Werewolf game.") +async def start(ctx): + if werewolf_game.running: + await ctx.message.channel.send("Sorry! A game is already running") return + werewolf_game.running = True + werewolf_game.set_channel(ctx.message.channel) - if message.content.startswith('$logout'): - await bot.logout() - return + await werewolf_game.game() - if message.content.startswith('$werewolf'): - - # start (only one instance running) - - if werewolf_game.running: - await message.channel.send("Sorry! A game is already running") - return - - werewolf_game.running = True - werewolf_game.set_channel(message.channel) +@bot.command(help="greets you", description="This just says hello back to the message author.") +async def hello(ctx): + await ctx.message.channel.send(f"Hello {ctx.message.author.name} :wave:") - await werewolf_game.game() +@bot.command(help="test bot responsiveness", description="This is a debug function to see if the bot listens to a command.") +async def ping(ctx): + print("pong") + await ctx.send("pong") + + +def developer(command): + @functools.wraps(command) + async def wrapper(ctx): + DEV_ID = 461892912821698562 + if ctx.message.author.id == DEV_ID: + await command(ctx) + else: + await ctx.send("This command is not for you!") + return wrapper + + +@bot.command(help="not for you", description="Shut down the bot.") +@developer +async def logout(ctx): + await bot.logout() + + +@bot.command(help="debug") +@developer +async def debug(ctx): + print("DEBUG") + print(ctx.message.author.id) - return werewolf_game = Werewolf_Game(bot) bot.run(TOKEN) diff --git a/src/werewolf_game.py b/src/werewolf_game.py index b01a931..e9e5d81 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -1,8 +1,9 @@ from random import shuffle import asyncio -from werewolf_roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Villiager, Tanner, Hunter, No_role +from werewolf_roles import Role, Werewolf, Minion, Tanner, Hunter from werewolf_players import Player, No_player + class Game: def __init__(self, bot): @@ -11,26 +12,22 @@ class Game: self.player_list = [] self.role_list = Role.role_set - def set_channel(self, channel): self.channel = channel - async def send(self, message): await self.channel.send(message) - async def receive(self, command): def check(msg): return msg.channel == self.channel and msg.content.startswith(command) - return await self.bot.wait_for('message', check = check) + return await self.bot.wait_for('message', check=check) def setup(self): self.werewolf_list = [] self.mason_list = [] - async def set_players(self): await self.send("Who is playing?") @@ -47,7 +44,6 @@ class Game: # send confirmation await self.send("Players: " + ", ".join(p.name() for p in self.player_list)) - async def set_roles(self): await self.send("With which roles do you want to play?") msg = await self.receive('$roles') @@ -69,7 +65,6 @@ class Game: # send confirmation await self.send("Roles: " + ", ".join(r.name() for r in self.role_list)) - def distribute_roles(self): shuffle(self.role_list) for i in range(len(self.player_list)): @@ -77,21 +72,20 @@ class Game: self.role_list[i].setPlayer(self.player_list[i]) self.middle_card = self.role_list[-3:] - self.active_role = sorted(self.role_list[:-3], key = lambda x: x.order) #necessary? - + self.active_role = sorted(self.role_list[:-3], key=lambda x: x.order) async def start_night(self): - await asyncio.gather( *[p.send("The night has begun") for p in self.player_list] ) + await asyncio.gather(*[p.send("The night has begun") for p in self.player_list]) async def send_role(self): - await asyncio.gather( *[p.send("Your role: " + p.night_role.name()) for p in self.player_list] ) + await asyncio.gather(*[p.send("Your role: " + p.night_role.name()) for p in self.player_list]) async def night_phases(self): - await asyncio.gather( *[r.phase1() for r in self.active_role] ) - await asyncio.gather( *[r.phase2() for r in self.active_role] ) - await asyncio.gather( *[r.phase3() for r in self.active_role] ) - await asyncio.gather( *[r.phase4() for r in self.active_role] ) - await asyncio.gather( *[r.phase5() for r in self.active_role] ) + await asyncio.gather(*[r.phase1() for r in self.active_role]) + await asyncio.gather(*[r.phase2() for r in self.active_role]) + await asyncio.gather(*[r.phase3() for r in self.active_role]) + await asyncio.gather(*[r.phase4() for r in self.active_role]) + await asyncio.gather(*[r.phase5() for r in self.active_role]) async def start_day(self): await self.send("The day has started") @@ -102,7 +96,7 @@ class Game: await self.receive('$vote') await self.send("Vote in DM") - await asyncio.gather( *[p.cast_vote(options) for p in self.player_list] ) + await asyncio.gather(*[p.cast_vote(options) for p in self.player_list]) await self.send("Votes\n\n" + '\n'.join(str(p) + " :arrow_right: " + str(p.vote) for p in self.player_list)) @@ -187,7 +181,6 @@ class Game: def end(self): self.running = False - async def game(self): try: @@ -202,7 +195,7 @@ class Game: await self.night_phases() await self.start_day() - #discussion timer + # discussion timer options = self.player_list + [No_player(self)] await self.vote(options) diff --git a/src/werewolf_players.py b/src/werewolf_players.py index 9086772..2d88af0 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -1,3 +1,6 @@ +from werewolf_roles import No_role + + class Player: @staticmethod @@ -8,9 +11,8 @@ class Player: p.game = game return p - @staticmethod - def swap(player_A, player_B): - player_A.day_role, player_B.day_role = player_B.day_role, player_A.day_role + def swap(self, player_B): + self.day_role, player_B.day_role = player_B.day_role, self.day_role def setRole(self, role): self.night_role = role @@ -29,13 +31,13 @@ class Player: await self.dm.send(message) async def ask_choice(self, options): - await self.send('\n'.join( "(" + str(i) + ") " + str(options[i]) for i in range(len(options)) )) + await self.send('\n'.join("(" + str(i) + ") " + str(options[i]) for i in range(len(options)))) async def receive_choice(self, options): def check(choice): - return choice.channel == self.dm and choice.content.isdigit() and 0 <= int(choice.content) < len(options) + return choice.channel == self.dm and choice.content.isdigit() and 0 <= int(choice.content) < len(options) - return int((await self.game.bot.wait_for('message', timeout=30.0, check = check)).content) + return int((await self.game.bot.wait_for('message', timeout=30.0, check=check)).content) async def get_choice(self, options): await self.ask_choice(options) @@ -44,6 +46,7 @@ class Player: async def cast_vote(self, options): self.vote = options[await self.get_choice(options)] + class No_player(Player): def __init__(self): diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py index 8d108cd..5185634 100644 --- a/src/werewolf_roles.py +++ b/src/werewolf_roles.py @@ -7,19 +7,19 @@ class Role: def setPlayer(self, player): self.player = player - async def phase1(self): # query stuff + doppelganger simulation + async def phase1(self): # query stuff + doppelganger simulation pass - async def phase2(self): # werewolf stuff + seer info + async def phase2(self): # werewolf stuff + seer info pass - async def phase3(self): # robber simulation & info + async def phase3(self): # robber simulation & info pass - async def phase4(self): # troublemaker simulation + async def phase4(self): # troublemaker simulation pass - async def phase5(self): # mostly sending info + drunk simulation + async def phase5(self): # mostly sending info + drunk simulation pass @staticmethod @@ -35,9 +35,11 @@ class Role: def __str__(self): return self.name() + class Doppelganger(Role): order = 1 + class Werewolf(Role): order = 2 @@ -107,9 +109,10 @@ class Robber(Role): self.choice = await self.player.get_choice(self.player.other()) async def phase3(self): - Player.swap(self.player, self.player.other()[self.choice]) + self.player.swap(self.player.other()[self.choice]) await self.player.send("You robbed: " + str(self.player.day_role)) + class Troublemaker(Role): order = 7 @@ -119,10 +122,11 @@ class Troublemaker(Role): self.B = await self.player.get_choice(self.player.other()) async def phase4(self): - Player.swap(self.player.other()[self.A], self.player.other()[self.B]) + self.player.other()[self.A].swap(self.player.other()[self.B]) # receive conformation await self.player.send("Received " + str(self.A) + " " + str(self.B)) + class Drunk(Role): order = 8 @@ -132,25 +136,31 @@ class Drunk(Role): async def phase5(self): self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role - #receive conformation + # receive conformation await self.player.send("Received " + str(self.choice)) + class Insomniac(Role): order = 9 async def phase5(self): await self.player.send("You are now: " + str(self.player.day_role)) + class Villiager(Role): order = 10 + class Tanner(Role): order = 11 + class Hunter(Role): order = 12 + class No_role(Role): order = 1000 -Role.role_set = [Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Villiager, Tanner, Hunter] \ No newline at end of file + +Role.role_set = [Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Villiager, Tanner, Hunter] From 749533626951eac8c0748da8847d6b7147873c8c Mon Sep 17 00:00:00 2001 From: bibin Date: Sun, 12 Apr 2020 23:01:47 +0200 Subject: [PATCH 04/26] commands instead of on_message --- src/werewolf_bot.py | 72 +++++++++++++++++++++++++++++++---------- src/werewolf_game.py | 70 +++++++++++++-------------------------- src/werewolf_players.py | 5 +-- src/werewolf_roles.py | 1 + 4 files changed, 79 insertions(+), 69 deletions(-) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index c61c82e..20cf469 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -13,7 +13,8 @@ if TOKEN is None: print("Missing discord token!") exit(1) -bot = commands.Bot(command_prefix=commands.when_mentioned_or('$werewolf ')) +PREFIX = '$w ' +bot = commands.Bot(command_prefix=commands.when_mentioned_or(PREFIX)) @bot.event @@ -22,34 +23,70 @@ async def on_ready(): print('We have logged in as {0.user}'.format(bot)) -@bot.event -async def on_message(message): - if message.author == bot.user: - return - await bot.process_commands(message) +# game commands + +game_instances = {} @bot.group(help="werewolf game", description="Prefix for the One Night Ultimate Werewolf commands.") async def game(ctx): if ctx.invoked_subcommand is None: - await bot.say('Invalid sub command passed...') + await ctx.send('Invalid sub command passed...') @game.command(help="start werewolf game", description="Start One Night Ultimate Werewolf game.") +async def setup(ctx): + if ctx.channel in game_instances: + await ctx.send("Game already setup in this channel") + else: + game_instances[ctx.channel] = Werewolf_Game(bot, ctx.channel) + + +def game_running(command): + @functools.wraps(command) + async def wrapper(ctx): + if ctx.channel not in game_instances: + await ctx.send(f"No game setup yet. Use {PREFIX}game setup") + elif game_instances[ctx.channel].running: + await ctx.send("Sorry! A game is already running") + else: + await command(ctx) + return wrapper + + +@game.command(help="start a round", description="Play one round of One Night Ultimate Werewolf") +@game_running async def start(ctx): - if werewolf_game.running: - await ctx.message.channel.send("Sorry! A game is already running") - return + await game_instances[ctx.channel].game() - werewolf_game.running = True - werewolf_game.set_channel(ctx.message.channel) - await werewolf_game.game() +@game.command(help="set players", description="Set the players for the next round.") +@game_running +async def players(ctx): + await game_instances[ctx.channel].set_players(ctx.message) + + +@game.command(help="set roles", description="Set the roles for the next round.") +@game_running +async def roles(ctx): + try: + await game_instances[ctx.channel].set_roles(ctx.message.content.split()[3:]) + except ValueError as error: + await ctx.send(error) + +# ONLY FOR TESTING +@game.command() +@game_running +async def vote(ctx): + await game_instances[ctx.channel].vote() + + +# smaller commands @bot.command(help="greets you", description="This just says hello back to the message author.") async def hello(ctx): - await ctx.message.channel.send(f"Hello {ctx.message.author.name} :wave:") + await ctx.send(f"Hello {ctx.message.author.name} :wave:") @bot.command(help="test bot responsiveness", description="This is a debug function to see if the bot listens to a command.") @@ -58,11 +95,14 @@ async def ping(ctx): await ctx.send("pong") +# developer commands + + def developer(command): @functools.wraps(command) async def wrapper(ctx): DEV_ID = 461892912821698562 - if ctx.message.author.id == DEV_ID: + if ctx.author.id == DEV_ID: await command(ctx) else: await ctx.send("This command is not for you!") @@ -81,6 +121,4 @@ async def debug(ctx): print("DEBUG") print(ctx.message.author.id) - -werewolf_game = Werewolf_Game(bot) bot.run(TOKEN) diff --git a/src/werewolf_game.py b/src/werewolf_game.py index e9e5d81..c0b4df9 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -6,65 +6,40 @@ from werewolf_players import Player, No_player class Game: - def __init__(self, bot): + def __init__(self, bot, channel): self.running = False self.bot = bot - self.player_list = [] - self.role_list = Role.role_set - - def set_channel(self, channel): self.channel = channel + self.player_list = [] + self.role_list = [] async def send(self, message): await self.channel.send(message) - async def receive(self, command): - def check(msg): - return msg.channel == self.channel and msg.content.startswith(command) - - return await self.bot.wait_for('message', check=check) - - def setup(self): - self.werewolf_list = [] - self.mason_list = [] - - async def set_players(self): - - await self.send("Who is playing?") - msg = await self.receive('$players') - - # use info from last round otherwise - if not msg.content.startswith('$players last'): - self.player_list = [await Player.make(mem, self) for mem in msg.mentions] - - # check conditions - if not 0 <= len(self.player_list) <= 10: - raise ValueError("Invalid number of players: " + str(len(self.player_list))) + async def set_players(self, msg): + self.player_list = [await Player.make(mem, self) for mem in msg.mentions] # send confirmation await self.send("Players: " + ", ".join(p.name() for p in self.player_list)) - async def set_roles(self): - await self.send("With which roles do you want to play?") - msg = await self.receive('$roles') - - # use info from last round otherwise - if not msg.content.startswith('$roles last'): - tmp_role = [Role.match(r, self) for r in msg.content.split()[1:]] - - # invalid input - if None in tmp_role: - raise ValueError("Invalid list of roles: " + str(tmp_role)) - - self.role_list = tmp_role - - # check condition - if not len(self.role_list) == (len(self.player_list) + 3): - raise ValueError("Invalid number of roles: " + str(len(self.role_list)) + " with " + str(len(self.player_list)) + " players") + async def set_roles(self, suggestions): + self.role_list = [Role.match(r, self) for r in suggestions] # raises ValueError # send confirmation await self.send("Roles: " + ", ".join(r.name() for r in self.role_list)) + def check(self): + 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): + self.werewolf_list = [] + self.mason_list = [] + self.running = True + def distribute_roles(self): shuffle(self.role_list) for i in range(len(self.player_list)): @@ -93,7 +68,7 @@ class Game: async def vote(self, options): # vote - await self.receive('$vote') + # replace with dm: await self.receive('$vote') await self.send("Vote in DM") await asyncio.gather(*[p.cast_vote(options) for p in self.player_list]) @@ -185,9 +160,8 @@ class Game: try: + self.check() self.setup() - await self.set_players() - await self.set_roles() self.distribute_roles() await self.start_night() await self.send_role() @@ -208,4 +182,4 @@ class Game: await self.send("Error: I got bored waiting for your input") finally: self.end() - await self.send("Game ended") + await self.send("Round ended") diff --git a/src/werewolf_players.py b/src/werewolf_players.py index 2d88af0..cacef4a 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -21,7 +21,7 @@ class Player: def name(self): return self.member.name - def __repr__(self): + def __str__(self): return self.name() def other(self): @@ -54,6 +54,3 @@ class No_player(Player): def name(self): return "no one" - - def __str__(self): - return self.name() diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py index 5185634..4e7173a 100644 --- a/src/werewolf_roles.py +++ b/src/werewolf_roles.py @@ -27,6 +27,7 @@ class Role: for role_class in Role.role_set: if message.casefold() == role_class.name(): return role_class(game) + raise ValueError(f"Invalid role: {message}") @classmethod def name(cls): From 42275c7ab59bb15c9c31ba7088e9ef7e60f7e821 Mon Sep 17 00:00:00 2001 From: bibin Date: Mon, 13 Apr 2020 20:55:17 +0200 Subject: [PATCH 05/26] beautified output --- src/werewolf_bot.py | 82 +++++++++++++++------ src/werewolf_game.py | 116 +++++++++++++++--------------- src/werewolf_players.py | 73 +++++++++++++------ src/werewolf_roles.py | 154 +++++++++++++++++----------------------- 4 files changed, 233 insertions(+), 192 deletions(-) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 20cf469..1816add 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -13,8 +13,10 @@ if TOKEN is None: print("Missing discord token!") exit(1) + PREFIX = '$w ' bot = commands.Bot(command_prefix=commands.when_mentioned_or(PREFIX)) +bot.remove_command('help') @bot.event @@ -23,76 +25,114 @@ async def on_ready(): print('We have logged in as {0.user}'.format(bot)) +@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) + + +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) + + # game commands game_instances = {} -@bot.group(help="werewolf game", description="Prefix for the One Night Ultimate Werewolf commands.") +@bot.group() async def game(ctx): if ctx.invoked_subcommand is None: - await ctx.send('Invalid sub command passed...') + await send_wrong(ctx, 'Invalid sub command passed...') -@game.command(help="start werewolf game", description="Start One Night Ultimate Werewolf game.") +@game.command() async def setup(ctx): if ctx.channel in game_instances: - await ctx.send("Game already setup in this channel") + 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") def game_running(command): @functools.wraps(command) async def wrapper(ctx): if ctx.channel not in game_instances: - await ctx.send(f"No game setup yet. Use {PREFIX}game setup") + await send_wrong(ctx, f"No game setup yet. Use {PREFIX}game setup") elif game_instances[ctx.channel].running: - await ctx.send("Sorry! A game is already running") + await send_wrong(ctx, "Sorry! A game is already running") else: await command(ctx) return wrapper -@game.command(help="start a round", description="Play one round of One Night Ultimate Werewolf") +def error_handling(command): + @functools.wraps(command) + async def wrapper(ctx): + try: + await command(ctx) + except ValueError as error: + await send_wrong(ctx, str(error)) + except asyncio.TimeoutError: + await send_wrong(ctx, "Error: I got bored waiting for your input") + return wrapper + + +@game.command() @game_running +@error_handling async def start(ctx): await game_instances[ctx.channel].game() -@game.command(help="set players", description="Set the players for the next round.") +@game.command() @game_running +@error_handling async def players(ctx): await game_instances[ctx.channel].set_players(ctx.message) -@game.command(help="set roles", description="Set the roles for the next round.") +@game.command() @game_running +@error_handling async def roles(ctx): - try: - await game_instances[ctx.channel].set_roles(ctx.message.content.split()[3:]) - except ValueError as error: - await ctx.send(error) + await game_instances[ctx.channel].set_roles(ctx.message.content.split()[3:]) # exclude commands # ONLY FOR TESTING @game.command() @game_running +@error_handling async def vote(ctx): await game_instances[ctx.channel].vote() # smaller commands - -@bot.command(help="greets you", description="This just says hello back to the message author.") +@bot.command() async def hello(ctx): - await ctx.send(f"Hello {ctx.message.author.name} :wave:") + await send_friendly(ctx, f"Hello {ctx.message.author.name} :wave:") -@bot.command(help="test bot responsiveness", description="This is a debug function to see if the bot listens to a command.") +@bot.command() async def ping(ctx): print("pong") - await ctx.send("pong") + await send_friendly(ctx, "pong") # developer commands @@ -105,17 +145,17 @@ def developer(command): if ctx.author.id == DEV_ID: await command(ctx) else: - await ctx.send("This command is not for you!") + await send_wrong(ctx, "This command is not for you!") return wrapper -@bot.command(help="not for you", description="Shut down the bot.") +@bot.command() @developer async def logout(ctx): await bot.logout() -@bot.command(help="debug") +@bot.command() @developer async def debug(ctx): print("DEBUG") diff --git a/src/werewolf_game.py b/src/werewolf_game.py index c0b4df9..5cf0b55 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -1,6 +1,7 @@ from random import shuffle import asyncio -from werewolf_roles import Role, Werewolf, Minion, Tanner, Hunter +import discord +from werewolf_roles import Role, Werewolf, Mason from werewolf_players import Player, No_player @@ -14,46 +15,47 @@ class Game: self.role_list = [] async def send(self, message): - await self.channel.send(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 Player.make(mem, self) for mem in msg.mentions] - - # send confirmation - await self.send("Players: " + ", ".join(p.name() for p in self.player_list)) + 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, self) for r in suggestions] # raises ValueError - - # send confirmation - await self.send("Roles: " + ", ".join(r.name() for r in self.role_list)) + 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 def check(self): 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): - self.werewolf_list = [] - self.mason_list = [] - self.running = True + self.role = dict() + for r in Role.__subclasses__(): + if r not in [Werewolf, Mason]: + r(self) + self.role[Werewolf] = [] + self.role[Mason] = [] + self.voting_list = self.player_list + [No_player()] + for c in self.voting_list: + c.tally = 0 def distribute_roles(self): shuffle(self.role_list) for i in range(len(self.player_list)): - self.player_list[i].setRole(self.role_list[i]) - self.role_list[i].setPlayer(self.player_list[i]) - + self.role_list[i](self, self.player_list[i]) self.middle_card = self.role_list[-3:] - self.active_role = sorted(self.role_list[:-3], key=lambda x: x.order) async def start_night(self): - await asyncio.gather(*[p.send("The night has begun") for p in self.player_list]) + await self.for_all_player(lambda p: p.send_normal("*The night has begun*")) async def send_role(self): - await asyncio.gather(*[p.send("Your role: " + p.night_role.name()) for p in self.player_list]) + await self.for_all_player(lambda p: p.send_info(f"Your role: **{p.night_role.name()}**")) async def night_phases(self): await asyncio.gather(*[r.phase1() for r in self.active_role]) @@ -65,42 +67,35 @@ class Game: async def start_day(self): await self.send("The day has started") - async def vote(self, options): + async def vote(self): # vote # replace with dm: await self.receive('$vote') await self.send("Vote in DM") - await asyncio.gather(*[p.cast_vote(options) for p in self.player_list]) - - await self.send("Votes\n\n" + '\n'.join(str(p) + " :arrow_right: " + str(p.vote) for p in self.player_list)) - - def tally(self, options): - for o in options: - o.tally = 0 + await self.for_all_player(lambda p: p.cast_vote(self.voting_list)) + def tally(self): for p in self.player_list: p.vote.tally += 1 - def who_dead(self, options): - - maxi = max(o.tally for o in options) + def who_dead(self): + 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 isinstance(d.day_role.copy, Hunter): + if d.day_role.is_Hunter: dead.append(d.vote) - return dead def who_won(self, dead): no_dead = (len(dead) == 0) - tanner_dead = any(isinstance(d.day_role.copy, Tanner) for d in dead) - werewolf_dead = any(isinstance(d.day_role.copy, Werewolf) for d in dead) - werewolf_in_game = any(isinstance(p.day_role.copy, Werewolf) for p in self.player_list) - minion_dead = any(isinstance(d.day_role.copy, Minion) for d in dead) - minion_in_game = any(isinstance(p.day_role.copy, Minion) for p in self.player_list) + tanner_dead = any(d.day_role.is_Tanner for d in dead) + werewolf_dead = any(d.day_role.is_Werewolf for d in dead) + werewolf_in_game = any(p.day_role.is_Werewolf for p in self.player_list) + minion_dead = any(d.day_role.is_Minion for d in dead) + minion_in_game = any(p.day_role.is_Minion for p in self.player_list) werewolf_won = False village_won = False @@ -132,35 +127,41 @@ class Game: if minion_in_game: werewolf_won = True + for p in self.player_list: + if p.day_role.is_Werewolf or p.day_role.is_Minion: + p.won = werewolf_won + elif p.day_role.is_Tanner: + p.won = p.dead + else: + p.won = village_won + return werewolf_won, village_won, tanner_won, dead async def result(self, werewolf_won, village_won, tanner_won, dead): if werewolf_won: - await self.send("Werewolves won!") - + winnning = ["Werewolves won!"] if village_won: - await self.send("Village won!") + winnning = ["Village won!"] + if tanner_won: + winnning.append(f"{sum(1 for d in dead if d.day_role.is_Tanner)} tanner won") - for d in dead: - if isinstance(d.day_role.copy, Tanner): - await self.send(str(d) + " won a tanner") + embed = discord.Embed(title=' and '.join(winnning), color=0x00ffff) + for p in self.player_list: + won_emoji = ":trophy:" if p.won else "frowning2" + dead_emoji = ":skull:" if p.dead else ":no_mouth:" + embed.add_field(name="", value=f"{won_emoji} {dead_emoji} {p.tally}:ballot_box: {str(p)} :sunrise_over_mountains:{str(p.day_role)} (:full_moon:{str(p.night_role)}) :point_right:{str(p.vote)}", inline=False) + embed.add_field(name="Middle cards", value=', '.join(str(r) for r in self.middle_card)) - await self.send(":skull: " + ', '.join(str(d) for d in dead)) - await self.send('\n'.join(":ballot_box " + str(p.tally) + " votes for " + str(p) + " who is " + str(p.day_role) + " (was " + str(p.night_role) + ") " for p in self.player_list)) - await self.send("Middle cards: " + ', '.join(str(r) for r in self.middle_card)) - - # debug - await self.send("Success") + await self.channel.send(embed=embed) def end(self): self.running = False async def game(self): - try: - self.check() + self.running = True self.setup() self.distribute_roles() await self.start_night() @@ -171,15 +172,10 @@ class Game: await self.start_day() # discussion timer - options = self.player_list + [No_player(self)] - await self.vote(options) - self.tally(options) - await self.result(*self.who_won(self.who_dead(options))) + await self.vote() + self.tally() + await self.result(*self.who_won(self.who_dead())) - except ValueError as error: - await self.send(error) - except asyncio.TimeoutError: - await self.send("Error: I got bored waiting for your input") + await self.send("Round ended") finally: self.end() - await self.send("Round ended") diff --git a/src/werewolf_players.py b/src/werewolf_players.py index cacef4a..9e1b3e5 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -1,4 +1,4 @@ -from werewolf_roles import No_role +import discord class Player: @@ -11,12 +11,8 @@ class Player: p.game = game return p - def swap(self, player_B): - self.day_role, player_B.day_role = player_B.day_role, self.day_role - def setRole(self, role): - self.night_role = role - self.day_role = role + self.day_role = self.night_role = role def name(self): return self.member.name @@ -24,33 +20,66 @@ class Player: 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(self, message): + async def send_normal(self, message): await self.dm.send(message) - async def ask_choice(self, options): - await self.send('\n'.join("(" + str(i) + ") " + str(options[i]) for i in range(len(options)))) + async def send_embed(self, desc, color): + await self.dm.send(embed=discord.Embed(description=desc, color=color)) - async def receive_choice(self, options): - def check(choice): - return choice.channel == self.dm and choice.content.isdigit() and 0 <= int(choice.content) < len(options) + async def send_wrong(self, message): + await self.send_embed(message, 0xff8000) - return int((await self.game.bot.wait_for('message', timeout=30.0, check=check)).content) + async def send_confirmation(self, message): + await self.send_embed(message, 0x00ff00) - async def get_choice(self, options): - await self.ask_choice(options) - return await self.receive_choice(options) + async def send_info(self, message): + await self.send_embed(message, 0x00ffff) - async def cast_vote(self, options): - self.vote = options[await self.get_choice(options)] + 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}```") + + async def check_num(self, choice, N): + if not choice.isdigit(): + await self.send_wrong(f"Your choice {choice} is not a number") + return False + if not 0 <= int(choice) < N: + await self.send_wrong(f"Your choice {choice} is not in range 0 - {N-1}") + return False + + async def receive_choice(self, options, n_ans=1): + while True: + def check(choice): + return choice.channel == self.dm + choice = (await self.game.bot.wait_for('message', timeout=30.0, check=check)).content.split() + + if not len(choice) == n_ans: + await self.send_wrong(f"Please give {n_ans} numbers not {len(choice)}") + continue + if not all(self.check_num(c, len(options)) for c in choice): + 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) + + async def cast_vote(self, question, options): + self.vote = options[await self.get_choice(question, options)] class No_player(Player): - - def __init__(self): - self.day_role = No_role() - def name(self): return "no one" diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py index 4e7173a..40b6d20 100644 --- a/src/werewolf_roles.py +++ b/src/werewolf_roles.py @@ -1,32 +1,25 @@ +from werewolf_players import No_player + + class Role: - - def __init__(self, game): + def __init__(self, game, player=No_player()): self.game = game - self.copy = self - - def setPlayer(self, player): self.player = player + self.player.setRole(self) + self.add_yourself() + self.is_Hunter = self.is_Tanner = self.is_Werewolf = self.is_Minion = False - async def phase1(self): # query stuff + doppelganger simulation - pass + def add_yourself(self): + self.game.role[type(self)] = self - async def phase2(self): # werewolf stuff + seer info - pass - - async def phase3(self): # robber simulation & info - pass - - async def phase4(self): # troublemaker simulation - pass - - async def phase5(self): # mostly sending info + drunk simulation - pass + async def send_role_list(self, cls): + await self.player.send_info(f"{cls}: {', '.join(str(p) for p in self.game.role[cls])}") @staticmethod - def match(message, game): - for role_class in Role.role_set: + def match(message): + for role_class in Role.__subclasses__(): if message.casefold() == role_class.name(): - return role_class(game) + return role_class raise ValueError(f"Invalid role: {message}") @classmethod @@ -38,58 +31,54 @@ class Role: class Doppelganger(Role): - order = 1 + pass class Werewolf(Role): - order = 2 + def __init__(self, game, player=No_player()): + super().__init__(game, player) + self.is_Werewolf = True - def setPlayer(self, player): - super().setPlayer(player) - self.game.werewolf_list.append(player) + def add_yourself(self): + self.game.role[Werewolf].append(self) - async def phase2(self): - if len(self.game.werewolf_list) >= 2: - await self.player.send("Werewolves: " + str(self.game.werewolf_list)) + async def phase(self): + if len(self.game.role[Werewolf]) >= 2: + await self.send_role_list(Werewolf) else: - await self.player.send("You are the only werewolf") - await self.player.send("Which card in the middle do you want to look at?") - self.choice = await self.player.get_choice(["left", "middle", "right"]) + await self.player.send_info("You are the only werewolf") + self.choice = await self.player.get_choice("Which card in the middle do you want to look at?", ["left", "middle", "right"]) # 0, 1, 2 - await self.player.send("A card in the middle is: " + self.game.middle_card[self.choice].name()) + await self.player.send_info(f"A card in the middle is: {self.game.middle_card[self.choice].name()}") class Minion(Role): - order = 3 + def __init__(self, game, player=No_player()): + super().__init__(game, player) + self.is_Minion = True - async def phase2(self): + async def send_info(self): if len(self.game.werewolf_list) == 0: - await self.player.send("There were no werewolves so you became one") + await self.player.send_info("There were no werewolves, so you need to kill a villager!") else: - await self.player.send("Werewolves: " + str(self.game.werewolf_list)) + await self.send_role_list(Werewolf) class Mason(Role): - order = 4 + def add_yourself(self): + self.game.role[Mason].append(self) - def setPlayer(self, player): - super().setPlayer(player) - self.game.mason_list.append(player) - - async def phase2(self): - await self.player.send("Mason " + str(self.game.mason_list)) + async def send_info(self): + await self.send_role_list(Mason) class Seer(Role): - order = 5 + async def query(self): + self.choice = await self.player.get_choice("Which 1 player card or 2 middle cards do you want to look at?", self.player.other() + ["left & middle", "middle & right", "left & right"]) - async def phase1(self): - await self.player.send("Which 1 player card or 2 middle cards do you want to look at?") - self.choice = await self.player.get_choice(self.player.other() + ["left & middle", "middle & right", "left & right"]) - - async def phase2(self): + async def send_info(self): if self.choice < len(self.player.other()): - await self.player.send(self.player.other()[self.choice].night_role) + await self.player.send_info(self.player.other()[self.choice].night_role) else: self.choice -= len(self.player.other()) if self.choice == 0: @@ -99,69 +88,56 @@ class Seer(Role): else: a, b = 0, 2 - await self.player.send(str(self.game.middle_card[a]) + " " + str(self.game.middle_card[b])) + await self.player.send_info(f"{self.game.middle_card[a]} {self.game.middle_card[b]}") class Robber(Role): - order = 6 + async def query(self): + self.choice = await self.player.get_choice("Which player do you want to rob?", self.player.other()) - async def phase1(self): - await self.player.send("Which player do you want to rob?") - self.choice = await self.player.get_choice(self.player.other()) - - async def phase3(self): + async def simulate(self): self.player.swap(self.player.other()[self.choice]) - await self.player.send("You robbed: " + str(self.player.day_role)) + + async def send_info(self): + await self.player.send_info(f"You robbed: {self.player.day_role}") class Troublemaker(Role): - order = 7 + async def query(self): + self.A, self.B = await self.player.get_double_choice("Who do you want to exchange? (send two numbers)", self.player.other()) - async def phase1(self): - await self.player.send("Who do you want to exchange? (send two separate numbers)") - self.A = await self.player.get_choice(self.player.other()) - self.B = await self.player.get_choice(self.player.other()) - - async def phase4(self): + async def simulate(self): self.player.other()[self.A].swap(self.player.other()[self.B]) - # receive conformation - await self.player.send("Received " + str(self.A) + " " + str(self.B)) class Drunk(Role): - order = 8 + async def query(self): + self.choice = await self.player.get_choice("Which card from the middle do you want to take?", ["left", "middle", "right"]) - async def phase1(self): - await self.player.send("Which card from the middle do you want to take?") - self.choice = await self.player.get_choice(["left", "middle", "right"]) - - async def phase5(self): - self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role - # receive conformation - await self.player.send("Received " + str(self.choice)) + async def simulate(self): + self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role # swap class Insomniac(Role): - order = 9 - - async def phase5(self): - await self.player.send("You are now: " + str(self.player.day_role)) + async def send_info(self): + await self.player.send_info(f"You are now: {self.player.day_role}") -class Villiager(Role): - order = 10 +class Villager(Role): + pass class Tanner(Role): - order = 11 + def __init__(self, game, player=No_player()): + super().__init__(game, player) + self.is_Tanner = True class Hunter(Role): - order = 12 + def __init__(self, game, player=No_player()): + super().__init__(game, player) + self.is_Hunter = True class No_role(Role): - order = 1000 - - -Role.role_set = [Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Villiager, Tanner, Hunter] + pass From e0029118f9f8d91e8b9b4b275ddd408e8629c1cb Mon Sep 17 00:00:00 2001 From: bibin Date: Tue, 14 Apr 2020 02:55:51 +0200 Subject: [PATCH 06/26] doppelganger + refactored code --- src/werewolf_bot.py | 2 +- src/werewolf_game.py | 47 ++++++++++++--------- src/werewolf_players.py | 18 ++++---- src/werewolf_roles.py | 94 ++++++++++++++++++++++++++++------------- 4 files changed, 103 insertions(+), 58 deletions(-) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 1816add..7ee894e 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -21,7 +21,7 @@ bot.remove_command('help') @bot.event async def on_ready(): - await bot.change_presence(status=discord.Status.idle, activity=discord.Game('One Night Ultimate Werewolf')) + 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)) diff --git a/src/werewolf_game.py b/src/werewolf_game.py index 5cf0b55..8abf581 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -1,7 +1,7 @@ from random import shuffle import asyncio import discord -from werewolf_roles import Role, Werewolf, Mason +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 @@ -37,13 +37,14 @@ class Game: def setup(self): self.role = dict() for r in Role.__subclasses__(): - if r not in [Werewolf, Mason]: + if r not in [Werewolf, Mason, No_role]: r(self) self.role[Werewolf] = [] self.role[Mason] = [] self.voting_list = self.player_list + [No_player()] for c in self.voting_list: c.tally = 0 + c.won = c.dead = False def distribute_roles(self): shuffle(self.role_list) @@ -58,11 +59,17 @@ class Game: await self.for_all_player(lambda p: p.send_info(f"Your role: **{p.night_role.name()}**")) async def night_phases(self): - await asyncio.gather(*[r.phase1() for r in self.active_role]) - await asyncio.gather(*[r.phase2() for r in self.active_role]) - await asyncio.gather(*[r.phase3() for r in self.active_role]) - await asyncio.gather(*[r.phase4() for r in self.active_role]) - await asyncio.gather(*[r.phase5() for r in self.active_role]) + 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 + await asyncio.gather(*[w.phase() for w in self.role[Werewolf]]) # slow + await asyncio.gather(*[w.send_info() for w in [self.role[Minion]] + self.role[Mason] + [self.role[Seer]]]) + await self.role[Robber].simulate() + await self.role[Robber].send_info() + await self.role[Troublemaker].simulate() + await self.role[Drunk].simulate() + await self.role[Insomniac].send_info() + await self.role[Doppelganger].insomniac() async def start_day(self): await self.send("The day has started") @@ -73,7 +80,7 @@ class Game: # replace with dm: await self.receive('$vote') await self.send("Vote in DM") - await self.for_all_player(lambda p: p.cast_vote(self.voting_list)) + await self.for_all_player(lambda p: p.cast_vote("Who do you want to kill?", self.voting_list)) def tally(self): for p in self.player_list: @@ -84,18 +91,18 @@ class Game: dead = [p for p in self.player_list if p.tally >= maxi] for d in dead: d.dead = True - if d.day_role.is_Hunter: + if d.day_role.is_role(Hunter): dead.append(d.vote) return dead def who_won(self, dead): no_dead = (len(dead) == 0) - tanner_dead = any(d.day_role.is_Tanner for d in dead) - werewolf_dead = any(d.day_role.is_Werewolf for d in dead) - werewolf_in_game = any(p.day_role.is_Werewolf for p in self.player_list) - minion_dead = any(d.day_role.is_Minion for d in dead) - minion_in_game = any(p.day_role.is_Minion for p in self.player_list) + tanner_dead = any(d.day_role.is_role(Tanner) for d in dead) + werewolf_dead = any(d.day_role.is_role(Werewolf) for d in dead) + werewolf_in_game = any(p.day_role.is_role(Werewolf) for p in self.player_list) + minion_dead = any(d.day_role.is_role(Minion) for d in dead) + minion_in_game = any(p.day_role.is_role(Minion) for p in self.player_list) werewolf_won = False village_won = False @@ -128,9 +135,9 @@ class Game: werewolf_won = True for p in self.player_list: - if p.day_role.is_Werewolf or p.day_role.is_Minion: + if p.day_role.is_role(Werewolf) or p.day_role.is_role(Minion): p.won = werewolf_won - elif p.day_role.is_Tanner: + elif p.day_role.is_role(Tanner): p.won = p.dead else: p.won = village_won @@ -144,14 +151,14 @@ class Game: if village_won: winnning = ["Village won!"] if tanner_won: - winnning.append(f"{sum(1 for d in dead if d.day_role.is_Tanner)} tanner won") + winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner won") embed = discord.Embed(title=' and '.join(winnning), color=0x00ffff) for p in self.player_list: - won_emoji = ":trophy:" if p.won else "frowning2" + won_emoji = ":trophy:" if p.won else ":frowning2:" dead_emoji = ":skull:" if p.dead else ":no_mouth:" - embed.add_field(name="", value=f"{won_emoji} {dead_emoji} {p.tally}:ballot_box: {str(p)} :sunrise_over_mountains:{str(p.day_role)} (:full_moon:{str(p.night_role)}) :point_right:{str(p.vote)}", inline=False) - embed.add_field(name="Middle cards", value=', '.join(str(r) for r in self.middle_card)) + 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) diff --git a/src/werewolf_players.py b/src/werewolf_players.py index 9e1b3e5..5c7f89e 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -45,24 +45,26 @@ 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}```") - async def check_num(self, choice, N): + def check_num(self, choice, N): if not choice.isdigit(): - await self.send_wrong(f"Your choice {choice} is not a number") - return False + raise ValueError(f"Your choice {choice} is not a number") if not 0 <= int(choice) < N: - await self.send_wrong(f"Your choice {choice} is not in range 0 - {N-1}") - return False + 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 + 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() if not len(choice) == n_ans: await self.send_wrong(f"Please give {n_ans} numbers not {len(choice)}") continue - if not all(self.check_num(c, len(options)) for c in choice): + 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)}") @@ -70,7 +72,7 @@ class Player: async def get_choice(self, question, options): await self.ask_choice(question, options) - return await self.receive_choice(options)[0] + return (await self.receive_choice(options))[0] async def get_double_choice(self, question, options): await self.ask_choice(question, options) diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py index 40b6d20..0c5b213 100644 --- a/src/werewolf_roles.py +++ b/src/werewolf_roles.py @@ -1,3 +1,4 @@ +import functools from werewolf_players import No_player @@ -7,13 +8,15 @@ class Role: self.player = player self.player.setRole(self) self.add_yourself() - self.is_Hunter = self.is_Tanner = self.is_Werewolf = self.is_Minion = False def add_yourself(self): self.game.role[type(self)] = self async def send_role_list(self, cls): - await self.player.send_info(f"{cls}: {', '.join(str(p) for p in self.game.role[cls])}") + await self.player.send_info(f"{cls.name()}: {', '.join(r.player.name() for r in self.game.role[cls])}") + + def is_role(self, cls): + return isinstance(self, cls) @staticmethod def match(message): @@ -22,6 +25,14 @@ class Role: return role_class raise ValueError(f"Invalid role: {message}") + @staticmethod + def no_player(func): + @functools.wraps(func) + async def wrapper(self, *args, **kwargs): + if not isinstance(self.player, No_player): + return await func(self, *args, **kwargs) + return wrapper + @classmethod def name(cls): return cls.__name__.casefold() @@ -31,17 +42,45 @@ class Role: class Doppelganger(Role): - pass + @Role.no_player + async def query(self): + self.choice = await self.player.get_choice("Which player role do you want to copy?", self.player.other()) + + @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}") + + @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]: + await self.copy_role.query(self) + if self.copy_role in [Robber, Troublemaker, Drunk]: + self.copy_role.simulate(self) + if self.copy_role in [Seer, Robber]: + 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) + + def is_role(self, cls): + return self.copy_role == cls class Werewolf(Role): - def __init__(self, game, player=No_player()): - super().__init__(game, player) - self.is_Werewolf = True - def add_yourself(self): self.game.role[Werewolf].append(self) + @Role.no_player async def phase(self): if len(self.game.role[Werewolf]) >= 2: await self.send_role_list(Werewolf) @@ -53,12 +92,9 @@ class Werewolf(Role): class Minion(Role): - def __init__(self, game, player=No_player()): - super().__init__(game, player) - self.is_Minion = True - + @Role.no_player async def send_info(self): - if len(self.game.werewolf_list) == 0: + if len(self.game.role[Werewolf]) == 0: await self.player.send_info("There were no werewolves, so you need to kill a villager!") else: await self.send_role_list(Werewolf) @@ -68,57 +104,61 @@ class Mason(Role): def add_yourself(self): self.game.role[Mason].append(self) + @Role.no_player async def send_info(self): await self.send_role_list(Mason) class Seer(Role): + @Role.no_player async def query(self): self.choice = await self.player.get_choice("Which 1 player card or 2 middle cards do you want to look at?", self.player.other() + ["left & middle", "middle & right", "left & right"]) + @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) else: - self.choice -= len(self.player.other()) - if self.choice == 0: - a, b = 0, 1 - elif self.choice == 1: - a, b = 1, 2 - else: - a, b = 0, 2 - + 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]}") class Robber(Role): + @Role.no_player async def query(self): self.choice = await self.player.get_choice("Which player do you want to rob?", self.player.other()) - async def simulate(self): + @Role.no_player + def simulate(self): self.player.swap(self.player.other()[self.choice]) + @Role.no_player async def send_info(self): await self.player.send_info(f"You robbed: {self.player.day_role}") class Troublemaker(Role): + @Role.no_player async def query(self): self.A, self.B = await self.player.get_double_choice("Who do you want to exchange? (send two numbers)", self.player.other()) - async def simulate(self): + @Role.no_player + def simulate(self): self.player.other()[self.A].swap(self.player.other()[self.B]) class Drunk(Role): + @Role.no_player async def query(self): self.choice = await self.player.get_choice("Which card from the middle do you want to take?", ["left", "middle", "right"]) - async def simulate(self): + @Role.no_player + def simulate(self): self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role # swap class Insomniac(Role): + @Role.no_player async def send_info(self): await self.player.send_info(f"You are now: {self.player.day_role}") @@ -128,15 +168,11 @@ class Villager(Role): class Tanner(Role): - def __init__(self, game, player=No_player()): - super().__init__(game, player) - self.is_Tanner = True + pass class Hunter(Role): - def __init__(self, game, player=No_player()): - super().__init__(game, player) - self.is_Hunter = True + pass class No_role(Role): From 240ad44e4cb6ce888ffed5ae2ddcd82ddd1ac28c Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Apr 2020 01:10:27 +0200 Subject: [PATCH 07/26] more generous role match function --- src/werewolf_bot.py | 2 +- src/werewolf_roles.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 7ee894e..0f398a3 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -159,6 +159,6 @@ async def logout(ctx): @developer async def debug(ctx): print("DEBUG") - print(ctx.message.author.id) + print(ctx.message.content) bot.run(TOKEN) diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py index 0c5b213..6c7ef22 100644 --- a/src/werewolf_roles.py +++ b/src/werewolf_roles.py @@ -1,4 +1,5 @@ import functools +from fuzzywuzzy import fuzz from werewolf_players import No_player @@ -20,10 +21,7 @@ class Role: @staticmethod def match(message): - for role_class in Role.__subclasses__(): - if message.casefold() == role_class.name(): - return role_class - raise ValueError(f"Invalid role: {message}") + return max(Role.__subclasses__(), key=lambda role_class: fuzz.ratio(message, role_class.name())) @staticmethod def no_player(func): @@ -160,7 +158,7 @@ class Drunk(Role): class Insomniac(Role): @Role.no_player async def send_info(self): - await self.player.send_info(f"You are now: {self.player.day_role}") + await self.player.send_info(f"You are: {self.player.day_role}") class Villager(Role): From caca8bc2e5917e2e22a5fcfc16e6e0d55e7d4a04 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Apr 2020 02:04:37 +0200 Subject: [PATCH 08/26] added stop command to restart round --- src/werewolf_bot.py | 25 ++++++++++++++++++++++--- src/werewolf_game.py | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 0f398a3..4c5ee4a 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -70,12 +70,21 @@ async def setup(ctx): await send_friendly(ctx, "This channel can now play Werewolf") -def game_running(command): +def channel_setup(command): @functools.wraps(command) async def wrapper(ctx): if ctx.channel not in game_instances: await send_wrong(ctx, f"No game setup yet. Use {PREFIX}game setup") - elif game_instances[ctx.channel].running: + else: + await command(ctx) + return wrapper + + +def game_running(command): + @functools.wraps(command) + @channel_setup + async def wrapper(ctx): + if game_instances[ctx.channel].running: await send_wrong(ctx, "Sorry! A game is already running") else: await command(ctx) @@ -98,7 +107,17 @@ def error_handling(command): @game_running @error_handling async def start(ctx): - await game_instances[ctx.channel].game() + game_instances[ctx.channel].game = bot.loop.create_task(game_instances[ctx.channel].round()) + + +@game.command() +@channel_setup +async def stop(ctx): + if not game_instances[ctx.channel].running: + await send_wrong(ctx, "No game is running") + else: + game_instances[ctx.channel].game.cancel() + await send_friendly(ctx, "Game canceled") @game.command() diff --git a/src/werewolf_game.py b/src/werewolf_game.py index 8abf581..4f5388f 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -165,7 +165,7 @@ class Game: def end(self): self.running = False - async def game(self): + async def round(self): try: self.check() self.running = True From be93817063bf907bd071f64187422db02cad1b66 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Apr 2020 14:20:30 +0200 Subject: [PATCH 09/26] more elegant setup function --- src/werewolf_game.py | 19 ++++++++++++------- src/werewolf_players.py | 4 ++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/werewolf_game.py b/src/werewolf_game.py index 4f5388f..6ba781d 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -36,15 +36,18 @@ class Game: def setup(self): self.role = dict() + # setting default value for r in Role.__subclasses__(): - if r not in [Werewolf, Mason, No_role]: + if r == No_role: + continue + if r in [Werewolf, Mason]: + self.role[r] = [] + else: r(self) - self.role[Werewolf] = [] - self.role[Mason] = [] self.voting_list = self.player_list + [No_player()] for c in self.voting_list: - c.tally = 0 - c.won = c.dead = False + c.reset() + self.time = 0 def distribute_roles(self): shuffle(self.role_list) @@ -74,6 +77,9 @@ class Game: async def start_day(self): await self.send("The day has started") + async def discussion_timer(self): + pass + async def vote(self): # vote @@ -168,8 +174,8 @@ class Game: async def round(self): try: self.check() - self.running = True self.setup() + self.running = True self.distribute_roles() await self.start_night() await self.send_role() @@ -177,7 +183,6 @@ class Game: await self.night_phases() await self.start_day() - # discussion timer await self.vote() self.tally() diff --git a/src/werewolf_players.py b/src/werewolf_players.py index 5c7f89e..8d4fd1c 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -14,6 +14,10 @@ class Player: def setRole(self, role): self.day_role = self.night_role = role + def reset(self): + self.tally = 0 + self.won = self.dead = False + def name(self): return self.member.name From e56a5d1629229dded33d2900bdfaf38be3e87804 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Apr 2020 17:23:24 +0200 Subject: [PATCH 10/26] fix middle_cards, fix async, discussion timer added --- src/werewolf_bot.py | 8 +------ src/werewolf_game.py | 48 ++++++++++++++++++++++++++--------------- src/werewolf_players.py | 6 ++++++ src/werewolf_roles.py | 8 +++---- 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 4c5ee4a..650840f 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -108,6 +108,7 @@ def error_handling(command): @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() @@ -133,13 +134,6 @@ async def players(ctx): async def roles(ctx): await game_instances[ctx.channel].set_roles(ctx.message.content.split()[3:]) # exclude commands -# ONLY FOR TESTING -@game.command() -@game_running -@error_handling -async def vote(ctx): - await game_instances[ctx.channel].vote() - # smaller commands diff --git a/src/werewolf_game.py b/src/werewolf_game.py index 6ba781d..f2a2e0f 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -13,6 +13,7 @@ class Game: self.channel = channel self.player_list = [] self.role_list = [] + self.discussion_time = 300 # seconds async def send(self, message): await self.channel.send(embed=discord.Embed(description=message, color=0x00ffff)) @@ -43,7 +44,7 @@ class Game: if r in [Werewolf, Mason]: self.role[r] = [] else: - r(self) + r(self).add_yourself() self.voting_list = self.player_list + [No_player()] for c in self.voting_list: c.reset() @@ -52,8 +53,10 @@ class Game: def distribute_roles(self): shuffle(self.role_list) for i in range(len(self.player_list)): - self.role_list[i](self, self.player_list[i]) - self.middle_card = self.role_list[-3:] + role_obj = self.role_list[i](self, self.player_list[i]) + self.player_list[i].setRole(role_obj) + role_obj.add_yourself() + self.middle_card = [r(self) for r in self.role_list[-3:]] async def start_night(self): await self.for_all_player(lambda p: p.send_normal("*The night has begun*")) @@ -78,14 +81,26 @@ class Game: await self.send("The day has started") async def discussion_timer(self): - pass + await self.send(f"You have {self.discussion_time / 60} minutes to discuss") + await asyncio.sleep(self.discussion_time / 2) + await self.send("Half of the time has passed") + await asyncio.sleep(self.discussion_time / 2 - 60) + await self.send("One minute remaining") + await asyncio.sleep(30) + await self.send("30 seconds remaining") + await asyncio.sleep(30) + + async def early_vote(self): + await self.for_all_player(lambda p: p.ready_to_vote()) + + async def discussion_finished(self): + done, pending = await asyncio.wait([self.discussion_timer(), self.early_vote()], return_when=asyncio.FIRST_COMPLETED) + for p in pending: + p.cancel() + await asyncio.wait(pending) async def vote(self): - - # vote - # replace with dm: await self.receive('$vote') await self.send("Vote in DM") - await self.for_all_player(lambda p: p.cast_vote("Who do you want to kill?", self.voting_list)) def tally(self): @@ -151,15 +166,17 @@ class Game: return werewolf_won, village_won, tanner_won, dead async def result(self, werewolf_won, village_won, tanner_won, dead): - + winnning = [] if werewolf_won: - winnning = ["Werewolves won!"] + winnning.append("Werewolves") if village_won: - winnning = ["Village won!"] + winnning.append("Village") if tanner_won: - winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner won") + winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner") + if len(winnning) == 0: + winnning = ["No one"] - embed = discord.Embed(title=' and '.join(winnning), color=0x00ffff) + embed = discord.Embed(title=f"{' and '.join(winnning)} won!", color=0x00ffff) for p in self.player_list: won_emoji = ":trophy:" if p.won else ":frowning2:" dead_emoji = ":skull:" if p.dead else ":no_mouth:" @@ -179,15 +196,12 @@ class Game: self.distribute_roles() await self.start_night() await self.send_role() - await self.night_phases() - await self.start_day() - + await self.discussion_finished() await self.vote() self.tally() await self.result(*self.who_won(self.who_dead())) - await self.send("Round ended") finally: self.end() diff --git a/src/werewolf_players.py b/src/werewolf_players.py index 8d4fd1c..13af25b 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -85,6 +85,12 @@ class Player: async def cast_vote(self, question, options): self.vote = options[await self.get_choice(question, options)] + async def ready_to_vote(self): + def check(msg): + return msg.channel == self.dm and msg.author == self.member and msg.content.casefold() == "vote" + await self.game.bot.wait_for('message', check=check) + await self.send_confirmation("You are ready to vote") + class No_player(Player): def name(self): diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py index 6c7ef22..ce14fb1 100644 --- a/src/werewolf_roles.py +++ b/src/werewolf_roles.py @@ -7,8 +7,6 @@ class Role: def __init__(self, game, player=No_player()): self.game = game self.player = player - self.player.setRole(self) - self.add_yourself() def add_yourself(self): self.game.role[type(self)] = self @@ -127,7 +125,7 @@ class Robber(Role): self.choice = await self.player.get_choice("Which player do you want to rob?", self.player.other()) @Role.no_player - def simulate(self): + async def simulate(self): self.player.swap(self.player.other()[self.choice]) @Role.no_player @@ -141,7 +139,7 @@ class Troublemaker(Role): self.A, self.B = await self.player.get_double_choice("Who do you want to exchange? (send two numbers)", self.player.other()) @Role.no_player - def simulate(self): + async def simulate(self): self.player.other()[self.A].swap(self.player.other()[self.B]) @@ -151,7 +149,7 @@ class Drunk(Role): self.choice = await self.player.get_choice("Which card from the middle do you want to take?", ["left", "middle", "right"]) @Role.no_player - def simulate(self): + async def simulate(self): self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role # swap From b474d410874023c39f43d57f88046bba364bf9ea Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Apr 2020 18:42:56 +0200 Subject: [PATCH 11/26] added decorator, decorator with args, time command added --- src/werewolf_bot.py | 66 ++++++++++++++++++++++++++++++-------------- src/werewolf_game.py | 19 +++++++++---- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 650840f..3ef0819 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -72,30 +72,41 @@ async def setup(ctx): def channel_setup(command): @functools.wraps(command) - async def wrapper(ctx): + async def wrapper(ctx, *args, **kwargs): if ctx.channel not in game_instances: await send_wrong(ctx, f"No game setup yet. Use {PREFIX}game setup") else: - await command(ctx) + 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): - if game_instances[ctx.channel].running: - await send_wrong(ctx, "Sorry! A game is already running") + 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) + await command(ctx, *args, **kwargs) return wrapper def error_handling(command): @functools.wraps(command) - async def wrapper(ctx): + async def wrapper(ctx, *args, **kwargs): try: - await command(ctx) + await command(ctx, *args, **kwargs) except ValueError as error: await send_wrong(ctx, str(error)) except asyncio.TimeoutError: @@ -104,7 +115,7 @@ def error_handling(command): @game.command() -@game_running +@game_not_running @error_handling async def start(ctx): game_instances[ctx.channel].game = bot.loop.create_task(game_instances[ctx.channel].round()) @@ -112,27 +123,39 @@ async def start(ctx): @game.command() +@game_running @channel_setup async def stop(ctx): - if not game_instances[ctx.channel].running: - await send_wrong(ctx, "No game is running") - else: - game_instances[ctx.channel].game.cancel() - await send_friendly(ctx, "Game canceled") + game_instances[ctx.channel].game.cancel() + await send_friendly(ctx, "Game canceled") @game.command() -@game_running +@game_not_running @error_handling async def players(ctx): await game_instances[ctx.channel].set_players(ctx.message) +@game.command() +@game_not_running +@error_handling +async def roles(ctx, *args): + await game_instances[ctx.channel].set_roles(args) + + +@game.command() +@game_not_running +@error_handling +async def minutes(ctx, i): + await game_instances[ctx.channel].set_time(i) + + @game.command() @game_running @error_handling -async def roles(ctx): - await game_instances[ctx.channel].set_roles(ctx.message.content.split()[3:]) # exclude commands +async def time(ctx): + await send_friendly(ctx, game_instances[ctx.channel].remaining_time_string()) # smaller commands @@ -153,10 +176,10 @@ async def ping(ctx): def developer(command): @functools.wraps(command) - async def wrapper(ctx): + async def wrapper(ctx, *args, **kwargs): DEV_ID = 461892912821698562 if ctx.author.id == DEV_ID: - await command(ctx) + await command(ctx, *args, **kwargs) else: await send_wrong(ctx, "This command is not for you!") return wrapper @@ -170,8 +193,9 @@ async def logout(ctx): @bot.command() @developer -async def debug(ctx): +async def debug(ctx, *args): print("DEBUG") - print(ctx.message.content) + print(ctx.args) + print(ctx.kwargs) bot.run(TOKEN) diff --git a/src/werewolf_game.py b/src/werewolf_game.py index f2a2e0f..255bef0 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -1,4 +1,5 @@ from random import shuffle +import time import asyncio import discord from werewolf_roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role @@ -29,6 +30,10 @@ class Game: self.role_list = [Role.match(r) for r in suggestions] # raises ValueError await self.send(f"Roles: {', '.join(r.name() for r in self.role_list)}") # send confirmation + async def set_time(self, msg): + self.discussion_time = int(msg) * 60 + 1 + await self.send(f"You have set the discussion time to {self.discussion_time//60} minutes") # send confirmation + def check(self): if not 0 <= len(self.player_list) <= 10: raise ValueError(f"Invalid number of players: {len(self.player_list)}") @@ -48,7 +53,6 @@ class Game: self.voting_list = self.player_list + [No_player()] for c in self.voting_list: c.reset() - self.time = 0 def distribute_roles(self): shuffle(self.role_list) @@ -80,14 +84,19 @@ class Game: async def start_day(self): await self.send("The day has started") + def remaining_time_string(self): + t = int(self.start_time + self.discussion_time - time.time()) + return f"{t//60} minute(s) and {t%60} second(s)" + async def discussion_timer(self): - await self.send(f"You have {self.discussion_time / 60} minutes to discuss") + self.start_time = time.time() + await self.send(f"You have {self.remaining_time_string()} to discuss") await asyncio.sleep(self.discussion_time / 2) - await self.send("Half of the time has passed") + await self.send(f"{self.remaining_time_string()} remaining") await asyncio.sleep(self.discussion_time / 2 - 60) - await self.send("One minute remaining") + await self.send(f"{self.remaining_time_string()} remaining") await asyncio.sleep(30) - await self.send("30 seconds remaining") + await self.send(f"{self.remaining_time_string()} remaining") await asyncio.sleep(30) async def early_vote(self): From 266b2fe81f935b7bce085690cce90000ea473251 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Apr 2020 19:21:00 +0200 Subject: [PATCH 12/26] update gitignore --- .gitignore | 2 ++ src/__pycache__/Role.cpython-37.pyc | Bin 7033 -> 0 bytes src/__pycache__/werewolf_game.cpython-37.pyc | Bin 9378 -> 0 bytes src/__pycache__/werewolf_players.cpython-37.pyc | Bin 3151 -> 0 bytes src/__pycache__/werewolf_roles.cpython-37.pyc | Bin 7026 -> 0 bytes src/werewolf_bot.py | 1 - 6 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 src/__pycache__/Role.cpython-37.pyc delete mode 100644 src/__pycache__/werewolf_game.cpython-37.pyc delete mode 100644 src/__pycache__/werewolf_players.cpython-37.pyc delete mode 100644 src/__pycache__/werewolf_roles.cpython-37.pyc diff --git a/.gitignore b/.gitignore index 7e21ea3..6b0dd66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ secrets.env .env venv +src/__pycache__/ +src/test.py \ No newline at end of file diff --git a/src/__pycache__/Role.cpython-37.pyc b/src/__pycache__/Role.cpython-37.pyc deleted file mode 100644 index d429ccc0be579eff70c83a561a2a0a7353c8592d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7033 zcmb_gTW=f372eq!mlP?<@>PkGZXL%Blg9EbX^<(gE!- zZGv`X1$2dJ3$!PzpsP&Vplfml^bFGu=(=oxZZPeFo|SW;=a{a5o|h*;pJ3Vpy&zA5 zKFM?y^eK57^l7GRpwGy&pwBWr1Nxj?1ii>~9rSbZdC<=@-B3cA%2Kv+l&dPrQ&m+{ zGpfE`S4J=^&)+keFLXwFp{s?qj226D%2*N7STUuL=GVrGB`s;=3XMn?S4UQ)hpQ{A zvW9C#&d55ho@~fjTp>%&$@#C1U9ke0@`POA`-VIzPhoymo`(E0sr^~hR~-`q=|@*; zAEL!Dov|Z!jKJ8zKfr46hG;d-)Lw57RBCsk&26G$)nC)}+P@9$;PU;ArHyEymR7r~ z-EisNZ|=N)SGA?mOY7Za>56#+7I{kz4 z^a)J;1TC|yIjpiIdaqm%xejzsfSZJxDiuD{OfK#vu+2I;~zb~jK*QH2+j!1CJkEM3y`n7@R^M)8CudTId{2Iv>Rf^KpF-xzDi5y+e# zvIA$#73UkEd&R}V+|;|DMclQ0LeAI&h#;2K;up~+#x9_=Bld)VwR>jIdTeLZ2=h+r z#9NzKKbwXaq>VhBuLb>XoTP3WItb-t%moIkLBOT~Dhm@34|2Rb(AULqVYJZ8<*}FE z{f^%FC%S~zD0=3ukm7-(uYkgOrI|RR@w3LBxC4K^ji4Q_=JQrDmXFv6MqEm;va&vY z5~;6}KkG4X>MM)JkTfVs<7Q}PraBgf(PJ!2&l>^S8U!cV>l*qx;Mvjt>>i0)TA(JD{yhKm3;}TGt zXdEa#VViJO8tb5qe}v3sw3vKu(o?eflDq_-;iT>-s5hc;pIl%n@{N)95oV^4X6lPM z7R&-S;fwj0RgOMJhTYl+Gh_Jb=Ns@x27n(%BOm}Lew-P6Rdo=F0TVTiqNb~V1_lQV zsVN$0Qb~C{W(&QC=`6LtLZ{4OBis1p%rdQ}Q)(IxyX>$<$40mecE2Q+ltIY|nntEf zKq{r*af6lfNjru`i1AZJD(!>AM4YM(h%W~zeF0O`5h?>1?~*r2puCM%#HKl3*OLMh zaRw9+Mx{tlM)a(ny(@kYB+S;(Wqv+1Psz0~VrJdz7(2q8cWG$^as#O~TiB2DG5tE; zDYDO$XI;hAuh1qvi^itRNsFygL}Z(N%4rSX&En@(Kdbh$C>Uw~nxBVJieT|!!F1T5VCrAW zA0E!;FJ1Dpuv*WjnVK@zt5`YGIRd)Yui^d>b5Z?r4=qR1f*8(_N;1#8I#vp~%UCJ@ z0YRHjaQ5nP5xOfL82U}7?9muKv1s(1LT%x22U&mmy|-|jqw`x2XKa+RtJ3_v`IY#} z4A1kJg|Uay&c{6IWOppXPF!3oJ#R-W;uI;}v&MMDq>;iPA}fn4Els%qzC{;(e$-)r z?QBX^3A*Hr}>ShjbnQu_d(6solm5O#MRtr35DT2b* zeYAK1ogq$%3+6dd%l@WkP8#4^3qh4iz@kLUvQklY7NM^Q4>$0}r)XnW@oY_FPvt7^ zXVK~^oP}U8#s!s;6bso|NU3BsiY|rKd1UM?qw%qeAlCSqi{uV+psdi*>dhYjTeA_K z0P8N6Q@|RhbXxvFVABRjalCSpJ6)H9FTn^`a&FTMz?sVCdpjHE;Sl**R@TE3y@__IT?q3cHigaf3I41W&dREzx(}7~ z^)Dc#>FIZI(Ntkgpzi}BKctQ#*-_;gooKhwa^PJw%XWF=Nci!gxGYXBgYOr_dkjBE z)7VqN&$*?y!Uy|g&neuEz5J*!HOl1^Vagh1@}-HB(3*}0N1TNcK>wz`h$ZNksk?*@ zhwTFxdqVvBGhQm zuF{m*vTVp3xk==c;6vX+{(_coXC#>D zV+pRbBCZ0u|Fe!Kxdlo8nvhI1eaIy9@~1lf3c=@KO{XTV2RYYs#AWx<1P->6CXH!Y ty(UI_j=B;Pr`|i+7t;4gPE1Ag?921k{K9-~erEm*T4TOG?_z}i{|Bl`>D2%L diff --git a/src/__pycache__/werewolf_game.cpython-37.pyc b/src/__pycache__/werewolf_game.cpython-37.pyc deleted file mode 100644 index dd64ba7b90bde899458325189f4f61fd05dd2b02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9378 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 diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 3ef0819..bfaf7cb 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -173,7 +173,6 @@ async def ping(ctx): # developer commands - def developer(command): @functools.wraps(command) async def wrapper(ctx, *args, **kwargs): From ec446785ee56b2ab5f1101b7c5e1ce20870ec035 Mon Sep 17 00:00:00 2001 From: bibin Date: Sun, 12 Jul 2020 13:17:39 +0200 Subject: [PATCH 13/26] 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 14/26] 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 15/26] 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 16/26] 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 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 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 17/26] 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 18/26] 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 19/26] 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 20/26] 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 21/26] 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" From cbaafe0b856be520e1a355e50f2a7509a19817e2 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 16:59:30 +0200 Subject: [PATCH 22/26] moved werewolf specific commands to werewolf.cog --- src/package/games/werewolf/cog.py | 18 ++++++++++++++++++ src/werewolf_bot.py | 25 ------------------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/package/games/werewolf/cog.py b/src/package/games/werewolf/cog.py index 65b88ca..d210fd1 100644 --- a/src/package/games/werewolf/cog.py +++ b/src/package/games/werewolf/cog.py @@ -37,6 +37,18 @@ class Werewolf_cog(Game_cog, commands.Cog): """registers all mentioned players for the game""" await super().players(ctx) + @werewolf.command() + @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() + @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() @commands.check(Game_cog.pre_game_check) async def start(self, ctx): @@ -49,6 +61,12 @@ class Werewolf_cog(Game_cog, commands.Cog): """aborts the current round of werewolf""" await super().stop(ctx) + @werewolf.command() + @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, Werewolf_game)) diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 9fb47b9..31a4a22 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -50,29 +50,4 @@ def error_handling(command): return wrapper ''' -''' - -# TODO: (specifig game) werewolf COG - -@game.command() -@game_not_running -@error_handling -async def roles(ctx, *args): - await game_instances[ctx.channel].set_roles(args) - - -@game.command() -@game_not_running -@error_handling -async def minutes(ctx, i): - await game_instances[ctx.channel].set_time(i) - - -@game.command() -@game_running -@error_handling -async def time(ctx): - await send_friendly(ctx, game_instances[ctx.channel].remaining_time_string()) -''' - bot.run(TOKEN) From 519aca51db0bece7bb716b77a12ca49242077d4f Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 17:12:10 +0200 Subject: [PATCH 23/26] resolved some name changes --- .gitignore | 2 +- src/package/games/werewolf/game.py | 4 ++-- src/package/games/werewolf/players.py | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 6b0dd66..4e78b90 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ secrets.env .env venv -src/__pycache__/ +__pycache__/ src/test.py \ No newline at end of file diff --git a/src/package/games/werewolf/game.py b/src/package/games/werewolf/game.py index 4d1b9ee..34fe62d 100644 --- a/src/package/games/werewolf/game.py +++ b/src/package/games/werewolf/game.py @@ -3,7 +3,7 @@ import time import asyncio import discord from .roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role -from .players import Player, No_player +from .players import Werewolf_player, No_player class Werewolf_game: @@ -27,7 +27,7 @@ class Werewolf_game: await asyncio.gather(*[call(p) for p in self.player_list]) async def set_players(self, msg): - self.player_list = [await Player.make(mem, self) for mem in msg.mentions] + 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): diff --git a/src/package/games/werewolf/players.py b/src/package/games/werewolf/players.py index a17ab9c..443f075 100644 --- a/src/package/games/werewolf/players.py +++ b/src/package/games/werewolf/players.py @@ -1,9 +1,7 @@ -import discord - """Has a single class: Werewolf_player""" # local import -from .player import Player +from ..players import Player class Werewolf_player(Player): From e2b8a20fa66468724fe8eda05ba785a55e855f55 Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 17:23:41 +0200 Subject: [PATCH 24/26] deleted pycache? --- .../__pycache__/developer.cpython-37.pyc | Bin 2736 -> 0 bytes .../__pycache__/send_message.cpython-37.pyc | Bin 1068 -> 0 bytes src/package/developer.py | 3 ++- .../games/__pycache__/game.cpython-37.pyc | Bin 751 -> 0 bytes .../games/__pycache__/game_cog.cpython-37.pyc | Bin 3939 -> 0 bytes .../werewolf/__pycache__/cog.cpython-37.pyc | Bin 2028 -> 0 bytes .../werewolf/__pycache__/game.cpython-37.pyc | Bin 10956 -> 0 bytes .../werewolf/__pycache__/players.cpython-37.pyc | Bin 5114 -> 0 bytes .../werewolf/__pycache__/roles.cpython-37.pyc | Bin 8612 -> 0 bytes 9 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 src/package/__pycache__/developer.cpython-37.pyc delete mode 100644 src/package/__pycache__/send_message.cpython-37.pyc delete mode 100644 src/package/games/__pycache__/game.cpython-37.pyc delete mode 100644 src/package/games/__pycache__/game_cog.cpython-37.pyc delete mode 100644 src/package/games/werewolf/__pycache__/cog.cpython-37.pyc delete mode 100644 src/package/games/werewolf/__pycache__/game.cpython-37.pyc delete mode 100644 src/package/games/werewolf/__pycache__/players.cpython-37.pyc delete mode 100644 src/package/games/werewolf/__pycache__/roles.cpython-37.pyc diff --git a/src/package/__pycache__/developer.cpython-37.pyc b/src/package/__pycache__/developer.cpython-37.pyc deleted file mode 100644 index e2450da55a8698910161c1eae73e628e69caee7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2736 zcmaJ@OK;mo5a#kF%9d?Ki4(L*Q>HD@sA%k@&ch(mG;Pu*DGKG_v;hPZ1jSuTlqs&V zyYhpTPtK`;e?bsHKK54hFXYhQP+oh=U&tk$A!S*1+fdl$YG-z4_L~>KnVXx@;Q7m5 z@V9JD`y2lpJ|-;g!jNCUL^Voct%+|vCOy5W6YYUU4Qf8usLAxMv1c?*YEk>S*0d;D z)9R(ya87H~N#=yS*9#IVL)3+H84Z-co-jb9_HaBIWxIYR^p~X>D@hr9*N^fQ}EoEf}w0$m=i-wXW7B3PPi6 zDt!b&0dCOu^+#}nhrq6EYK%(AbpUQ5#l6f%dj4_3+(y*yDEG%$MZG{V_i17DI^5Ql zDo_KNIYFqR%}DK}A3SAlC)i|e%-e0iJWAX^y1!gq8b~H~>rM_$wh(rLq|N-kV3H*v z%StqoAr~~W*9wU?I4eq6W1f446=DpS*${Ngq641?duft&s{bNtLA4 zBWD*Z#{zTW>W|>akc^-{3`v)C!QC&QM!cN900yklNbQF#j=7tbTyG29<<(8G2#2#$ zk4XsuC{E))GXeDLdI9%Y*g0vi)j=^xDZ)}u7mh5=X_?+`gTvkfmSZ+!h~}^(l;bqY zrN2{MTkt{u{r^^%(~jwwxb_U3C~%o_B_g3WGPBPUv=FcVF5PAy3d6;TY9pOLx+#*5 zX~rDQZK5EZ4yM|qM;YbU_+|#0Thq_g;{(E;y#{IJrxec;feAxa4o?26S;qf zNkQII$aQM$>$}h*cA7EdlkZ{MP0GD}4&hEfpnn2Xf$l;Hi&j+fe=f}-ucA?5V^ZBIf0 zW!|l;H&$=1TwPgNy>;#8&6R7bH|}I+17i6#hD2KNS^_h{OXmfTndgpc@p@Xh zycI~#rDBk5)YC<;&!P7C0*j#MN~q;LVUW~$NN3h&5D!>pLkEF!1!--uUW?ImuCU6J z8-qCZ>Sa*@#hLXj60>qZslOhvm}Z7lqR2d}J&0h#W=FnZu@UhOc+GBXfu zDL%zz8Nl&#=4^&S*&DA76|^!dhXHK?S^vse%36c=Wso6Jre@4TjVXS2NsUzXd8{~7 zd7Sfyh#Wrn#_C^9qd@pk6Kk%SKd&{jjjIV6N-w0aKhd`HDL!52n+g3q8Nvmx?|KV$F7=ZOq)!` zLe6)r2Vo7ohX>Il=sB8#uZunC(c?}GUl@8;!l(&(J3kHu3lE*J036TMp#LR`%0VRJ uD_Gav=2cLb}bvP1TfkfK; z2PpX)An_9@+*0uuXu`~1OfDh?Vyt<2XJ@}}b{`K0U54`QdtZLxjQyab5rM%Ds@+5b z40z5G_MCwS;)H?t;3XXRh(-QSTxUZ?wh<459aMWCjb>QM5)XJHR=X1)d}y7pqy=s0 zpl?F}8|XXG#g4tYKhg!rnYK2a>f;Bm_c9yaOUJe{sj8w-<*Bvdq%@($x~!N|JWH!e zLwLT)D#AcQp7aT-CAv$#WD*(SNqiXbT8zso@@n7ed}4_8XJ%UHem#x%%9)PG**GiW z(W~99mpTP);%QdJ3vKkG%-`xQ+#Oq^;(4mxAljH9HPE-`M|B`&R%Dfw0ReER`0?#^ zg4<1n^09=3JVE%1YC|+jzRxPY?=8Kh0DkI$cglB>!wbe&OvDf=^gvr>vU!!2McqJDU9l?Lk8Gxy&&bXqM`^{)0^gAL5x1XiX^%Uwsyb?RZ>@VIk{ z$3=~zIg*h7!{ja|OZJg&0AIr=afyL9%-9*jJ25w~ED{cuxrVX1PKLUy0UEf&CMH8p z`4N>hm^S~$)Rg@vtoLXC3~M!~g(-{a%{5F{%*oXZqktYrkrP}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- diff --git a/src/package/games/__pycache__/game_cog.cpython-37.pyc b/src/package/games/__pycache__/game_cog.cpython-37.pyc deleted file mode 100644 index bdaa947a65ce717c7c73113221eca19559709f90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3939 zcmb7HTW{RP73T1^l(egx<%_J;U@nEdfi0y;fCPoxT-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 diff --git a/src/package/games/werewolf/__pycache__/cog.cpython-37.pyc b/src/package/games/werewolf/__pycache__/cog.cpython-37.pyc deleted file mode 100644 index 5ddac64b75f66da201e15a39a265b775da6eed03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2028 zcmbVNOOM+&5GM7o<<~xvO50{j) ziQ(HWkW>GIeQSS5|AnqS?O*7rGnC}6w?z)2K#Cel!|(fMhWcSJ=rU}-w+_Xb%h+G^ z&@_Bz-(%M=aF9&$grz*-oJmXCiIv)cjdMr3iIcj4%h^jNTe5w_WE*VY7p-e2XiHCa zZdl;S?l~LwZqXAP^3sd)Gz~MUZ>`$!GE6~4`RtZghQVu4aFr)hF~gOJzH1ucqNX=E z01LQefhBohgLB~$w%<6Rx?$HmXhCb!-m>>>$_+LTZQdOlp(9)8j;$+u#@4ND;B0!( zzZh(}@zR$ATE%Y;?yt)Oc}TnB+TnZVu;2(eAC>(b738-XznaI|*KsyWpvW^nNjIYL-FfdY?=@4vQ_O++LOE} zdo$SWF|= zRWw?J(PcP;5wX$D`gDX^oi5&$UIS%7;0+^t_2_kz9Q0Q6#vWe>U%#eLKE4vX) zVQnqs$_-aeTvuP8LncvK)H@B0%HaexG(zj;q{-7=JP73&6>;@)0jhM&mo405XsJHl z6yd~}bc8UG6~btu4r%QH4%8bS$9p`;)kkso#-!AAnN?{LDF_R|gvv~YAUR!xSp+o) zOtokpW*H>NLmeX=Bm>2Aq5exlJtWu^)QZ3f&OX835O}ahzk(LUcHc2*~QYS%9IFw@~_s+`haRvG>Tqpu1&UsS%t zT+gR|Q!!%`+PfgCSnmRknL!_ac?ZP3kkj(;xb%d`FjPX6T_Mt3E)$yfh4^(DCiO~N z2$@G%5LCsbJ3}}Nbwu-aU1p|06eXFWXq9d>hv-rfnc!fd!2AnDWh8MF|(uhf>mi%Q&bih%5T1-c!=BIwgPR4CCf`JnWP`S)KZ)@U}3@1RkY|@kzu> nhaQdR(#AhSwUN=tgz4Xbc$VIGmHN+U8|~rHxBI+rpM3ETu@>Ws diff --git a/src/package/games/werewolf/__pycache__/game.cpython-37.pyc b/src/package/games/werewolf/__pycache__/game.cpython-37.pyc deleted file mode 100644 index 3217184ff79958512768d4b9642dd6568c44b867..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10956 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 From 14037bf5d8a51847db12824bb0a2bca7cc200cbc Mon Sep 17 00:00:00 2001 From: bibin Date: Wed, 15 Jul 2020 18:32:29 +0200 Subject: [PATCH 25/26] small fixes + correct checks --- src/package/developer.py | 2 +- src/package/games/game_cog.py | 15 ++++++++++----- src/package/games/werewolf/cog.py | 19 ++++++++++--------- src/package/games/werewolf/game.py | 3 ++- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/package/developer.py b/src/package/developer.py index c54a73a..b572206 100644 --- a/src/package/developer.py +++ b/src/package/developer.py @@ -13,7 +13,7 @@ class Developer(commands.Cog): 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)) - async def is_dev(ctx): + async def is_dev(self, ctx): if ctx.author.id == 461892912821698562: return True await ctx.send("This command is not for you!") diff --git a/src/package/games/game_cog.py b/src/package/games/game_cog.py index 7c9083e..4744b5a 100644 --- a/src/package/games/game_cog.py +++ b/src/package/games/game_cog.py @@ -5,6 +5,7 @@ from typing import Dict, Type # discord imports import discord +from discord.ext import commands # local imports from ..send_message import Send_message @@ -17,7 +18,7 @@ class Game_cog(Send_message): def __init__(self, bot, game_cls: Type[Game]): self.bot = bot self.game_cls = game_cls - self.game_instances = Dict[discord.TextChannel, self.game_cls] + self.game_instances = {} # TODO: type hint? Dict[discord.TextChannel, self.game_cls] async def setup_check(self, ctx): if ctx.channel not in self.game_instances: @@ -60,8 +61,10 @@ class Game_cog(Send_message): embed.set_footer(text="Have fun!") await ctx.send(embed=embed) - async def pre_game_check(self, ctx): - return self.setup_check(ctx) and self.not_running_check(ctx) + 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 players(self, ctx): await self.game_instances[ctx.channel].set_players(ctx.message) @@ -70,8 +73,10 @@ 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 - async def in_game_check(self, ctx): - return self.setup_check(ctx) and self.running_check(ctx) + 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 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 d210fd1..de02287 100644 --- a/src/package/games/werewolf/cog.py +++ b/src/package/games/werewolf/cog.py @@ -19,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, Werewolf_game) + await super().info(ctx) @werewolf.command() async def setup(self, ctx): """This function creates an game instance for this channel""" - await super().setup(ctx, Werewolf_game) + await super().setup(ctx) @werewolf.command() async def reset(self, ctx): @@ -32,37 +32,38 @@ class Werewolf_cog(Game_cog, commands.Cog): await super().reset(ctx) @werewolf.command() - @commands.check(Game_cog.pre_game_check) + @Game_cog.pre_game() 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) + @Game_cog.pre_game() async def roles(self, ctx, *args): """registers roles you want to play with""" await self.game_instances[ctx.channel].set_roles(args) @werewolf.command() - @commands.check(Game_cog.pre_game_check) - async def minutes(self, ctx, i): + @Game_cog.pre_game() + async def minutes(self, ctx, i: int): """set discussion time""" await self.game_instances[ctx.channel].set_time(i) @werewolf.command() - @commands.check(Game_cog.pre_game_check) + @Game_cog.pre_game() 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() - @commands.check(Game_cog.in_game_check) + @Game_cog.in_game() async def stop(self, ctx): """aborts the current round of werewolf""" await super().stop(ctx) @werewolf.command() - @commands.check(Game_cog.in_game_check) + @Game_cog.in_game() 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()) diff --git a/src/package/games/werewolf/game.py b/src/package/games/werewolf/game.py index 34fe62d..2be4aab 100644 --- a/src/package/games/werewolf/game.py +++ b/src/package/games/werewolf/game.py @@ -39,6 +39,7 @@ class Werewolf_game: 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 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): @@ -62,7 +63,7 @@ class Werewolf_game: shuffle(self.role_list) for i in range(len(self.player_list)): role_obj = self.role_list[i](self, self.player_list[i]) - self.player_list[i].setRole(role_obj) + self.player_list[i].set_role(role_obj) role_obj.add_yourself() self.middle_card = [r(self) for r in self.role_list[-3:]] From a4eaa141ace4830576d8ef26df60eb6a6cbbb3c8 Mon Sep 17 00:00:00 2001 From: bibin Date: Mon, 10 Aug 2020 22:28:20 +0200 Subject: [PATCH 26/26] start refactoring again (this time abstracting Discord API and removing dependencies of the game classe --- src/interaction/discord_wrapper.py | 57 +++++++++++++++++++++++++ src/interaction/template.py | 57 +++++++++++++++++++++++++ src/package/developer.py | 11 +---- src/package/games/game.py | 33 ++++++++++++++- src/package/games/game_cog.py | 10 +++-- src/package/games/werewolf/cog.py | 4 +- src/package/games/werewolf/game.py | 68 ++++++++++++++++-------------- src/werewolf_bot.py | 5 ++- 8 files changed, 195 insertions(+), 50 deletions(-) create mode 100644 src/interaction/discord_wrapper.py create mode 100644 src/interaction/template.py diff --git a/src/interaction/discord_wrapper.py b/src/interaction/discord_wrapper.py new file mode 100644 index 0000000..3f21e9f --- /dev/null +++ b/src/interaction/discord_wrapper.py @@ -0,0 +1,57 @@ +"""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 new file mode 100644 index 0000000..5cdcdea --- /dev/null +++ b/src/interaction/template.py @@ -0,0 +1,57 @@ +"""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 b572206..920a6df 100644 --- a/src/package/developer.py +++ b/src/package/developer.py @@ -13,7 +13,7 @@ class Developer(commands.Cog): 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)) - async def is_dev(self, ctx): + async def is_dev(ctx): if ctx.author.id == 461892912821698562: return True await ctx.send("This command is not for you!") @@ -41,15 +41,6 @@ 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 7a4ccf6..ac8cf93 100644 --- a/src/package/games/game.py +++ b/src/package/games/game.py @@ -1,20 +1,49 @@ +"""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 round(self): + 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 - async def set_players(self): + def setup(self): + pass + + def end(self): + self.running = False + + async def round(self): pass diff --git a/src/package/games/game_cog.py b/src/package/games/game_cog.py index 4744b5a..28a5886 100644 --- a/src/package/games/game_cog.py +++ b/src/package/games/game_cog.py @@ -1,7 +1,7 @@ """Has a single class: Game_cog""" # standard library imports -from typing import Dict, Type +from typing import Dict # discord imports import discord @@ -15,9 +15,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""" - def __init__(self, bot, game_cls: Type[Game]): + game_cls = Game + + def __init__(self, bot): self.bot = bot - self.game_cls = game_cls self.game_instances = {} # TODO: type hint? Dict[discord.TextChannel, self.game_cls] async def setup_check(self, ctx): @@ -45,7 +46,7 @@ class Game_cog(Send_message): async def reset(self, ctx): """This function deletes the game instance for this channel""" - if self.setup_check(ctx): + if await self.setup_check(ctx): del self.game_instances[ctx.channel] # TODO: better info message @@ -61,6 +62,7 @@ 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) diff --git a/src/package/games/werewolf/cog.py b/src/package/games/werewolf/cog.py index de02287..210d7b5 100644 --- a/src/package/games/werewolf/cog.py +++ b/src/package/games/werewolf/cog.py @@ -11,6 +11,8 @@ 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()? @@ -70,4 +72,4 @@ class Werewolf_cog(Game_cog, commands.Cog): def setup(bot): - bot.add_cog(Werewolf_cog(bot, Werewolf_game)) + bot.add_cog(Werewolf_cog(bot)) diff --git a/src/package/games/werewolf/game.py b/src/package/games/werewolf/game.py index 2be4aab..dee4564 100644 --- a/src/package/games/werewolf/game.py +++ b/src/package/games/werewolf/game.py @@ -1,54 +1,54 @@ +"""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: +class Werewolf_game(Game): + """This class simulates One Night Ultimate Werewolf with the help of Werewolf_player and Werewolf_role""" @classmethod def name(cls): return "One Night Ultimate Werewolf" + player_cls = Werewolf_player + def __init__(self, bot, channel): - self.running = False - self.bot = bot - self.channel = channel - self.player_list = [] + super().__init__(bot, channel) 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, msg): - self.discussion_time = int(msg) * 60 + 1 + async def set_time(self, minutes: int): + self.discussion_time = minutes * 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 0 <= len(self.player_list) <= 10: + if not 1 <= 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,6 +61,7 @@ class Werewolf_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) @@ -68,7 +69,7 @@ class Werewolf_game: self.middle_card = [r(self) for r in self.role_list[-3:]] async def start_night(self): - await self.for_all_player(lambda p: p.send_normal("*The night has begun*")) + await self.for_all_player(lambda p: p.send_normal(f"*The night has begun* {':full_moon:'*10}")) async def send_role(self): await self.for_all_player(lambda p: p.send_info(f"Your role: **{p.night_role.name()}**")) @@ -92,9 +93,10 @@ class Werewolf_game: def remaining_time_string(self): t = int(self.start_time + self.discussion_time - time.time()) - return f"{t//60} minute(s) and {t%60} second(s)" + return f"{t//60:02d}:{t%60:02d}" 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) @@ -123,8 +125,8 @@ class Werewolf_game: p.vote.tally += 1 def who_dead(self): - maxi = max(c.tally for c in self.voting_list) - dead = [p for p in self.player_list if p.tally >= maxi] + 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 for d in dead: d.dead = True if d.day_role.is_role(Hunter): @@ -153,10 +155,8 @@ class Werewolf_game: else: if tanner_dead: tanner_won = True - if werewolf_dead: village_won = True - else: if werewolf_dead: village_won = True @@ -187,22 +187,29 @@ class Werewolf_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") + winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner") # number of tanners dead 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 ":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) + 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) 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() @@ -217,6 +224,5 @@ class Werewolf_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 31a4a22..94c966e 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.3' +__version__ = '0.4' __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('$w ')) +bot = commands.Bot(command_prefix=commands.when_mentioned_or('$b ')) bot.load_extension('package.developer') bot.load_extension('package.games.werewolf.cog') @@ -32,6 +32,7 @@ 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