220 lines
7.0 KiB
Python
220 lines
7.0 KiB
Python
from random import shuffle
|
|
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
|
|
|
|
|
|
class Werewolf_game:
|
|
|
|
name = "One Night Ultimate Werewolf"
|
|
|
|
def __init__(self, bot, channel):
|
|
self.running = False
|
|
self.bot = bot
|
|
self.channel = channel
|
|
self.player_list = []
|
|
self.role_list = []
|
|
self.discussion_time = 301 # seconds
|
|
|
|
async def send(self, message):
|
|
await self.channel.send(embed=discord.Embed(description=message, color=0x00ffff))
|
|
|
|
async def for_all_player(self, call):
|
|
await asyncio.gather(*[call(p) for p in self.player_list])
|
|
|
|
async def set_players(self, msg):
|
|
self.player_list = [await 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
|
|
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)}")
|
|
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.role = dict()
|
|
# setting default value
|
|
for r in Role.__subclasses__():
|
|
if r == No_role:
|
|
continue
|
|
if r in [Werewolf, Mason]:
|
|
self.role[r] = []
|
|
else:
|
|
r(self).add_yourself()
|
|
self.voting_list = self.player_list + [No_player()]
|
|
for c in self.voting_list:
|
|
c.reset()
|
|
|
|
def distribute_roles(self):
|
|
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)
|
|
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*"))
|
|
|
|
async def send_role(self):
|
|
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
|
|
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")
|
|
|
|
def remaining_time_string(self):
|
|
t = int(self.start_time + self.discussion_time - time.time())
|
|
return f"{t//60} minute(s) and {t%60} second(s)"
|
|
|
|
async def discussion_timer(self):
|
|
self.start_time = time.time()
|
|
await self.send(f"You have {self.remaining_time_string()} to discuss")
|
|
await asyncio.sleep(self.discussion_time / 2)
|
|
await self.send(f"{self.remaining_time_string()} remaining")
|
|
await asyncio.sleep(self.discussion_time / 2 - 60)
|
|
await self.send(f"{self.remaining_time_string()} remaining")
|
|
await asyncio.sleep(30)
|
|
await self.send(f"{self.remaining_time_string()} remaining")
|
|
await asyncio.sleep(30)
|
|
|
|
async def early_vote(self):
|
|
await self.for_all_player(lambda p: p.ready_to_vote())
|
|
|
|
async def discussion_finished(self):
|
|
done, pending = await asyncio.wait([self.discussion_timer(), self.early_vote()], return_when=asyncio.FIRST_COMPLETED)
|
|
for p in pending:
|
|
p.cancel()
|
|
await asyncio.wait(pending)
|
|
|
|
async def vote(self):
|
|
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):
|
|
for p in self.player_list:
|
|
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]
|
|
for d in dead:
|
|
d.dead = True
|
|
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_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
|
|
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
|
|
|
|
for p in self.player_list:
|
|
if p.day_role.is_role(Werewolf) or p.day_role.is_role(Minion):
|
|
p.won = werewolf_won
|
|
elif p.day_role.is_role(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):
|
|
winnning = []
|
|
if werewolf_won:
|
|
winnning.append("Werewolves")
|
|
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")
|
|
if len(winnning) == 0:
|
|
winnning = ["No one"]
|
|
|
|
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)
|
|
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()
|
|
self.setup()
|
|
self.running = True
|
|
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()
|