Compare commits
No commits in common. "master" and "deploy" have entirely different histories.
|
@ -1,5 +1,3 @@
|
|||
secrets.env
|
||||
.env
|
||||
venv
|
||||
__pycache__/
|
||||
src/test.py
|
|
@ -1,57 +0,0 @@
|
|||
"""This module is a wrapper for messaging functionality Discord to play text based multi-player games"""
|
||||
|
||||
# discord imports
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
class Client:
|
||||
def create_task():
|
||||
pass
|
||||
|
||||
|
||||
class User:
|
||||
def name() -> str:
|
||||
pass
|
||||
|
||||
def DM() -> DM:
|
||||
pass
|
||||
|
||||
|
||||
class Message:
|
||||
def mentions() -> User:
|
||||
pass
|
||||
|
||||
|
||||
class Communication:
|
||||
def send():
|
||||
pass
|
||||
|
||||
def send_embed():
|
||||
pass
|
||||
|
||||
def command():
|
||||
pass
|
||||
|
||||
def wait_for_message() -> Message:
|
||||
pass
|
||||
|
||||
|
||||
class DM(Communication):
|
||||
pass
|
||||
|
||||
|
||||
class Room(Communication):
|
||||
pass
|
||||
|
||||
|
||||
class Context:
|
||||
pass
|
||||
|
||||
|
||||
def choices():
|
||||
pass
|
||||
|
||||
|
||||
def choice():
|
||||
pass
|
|
@ -1,57 +0,0 @@
|
|||
"""This module is a wrapper template for messaging functionality to play text based multi-player games"""
|
||||
|
||||
|
||||
class Interaction:
|
||||
def query(communication, question: str, options: list, exp_ans=1):
|
||||
pass
|
||||
|
||||
|
||||
class Client:
|
||||
def create_task(func):
|
||||
pass
|
||||
|
||||
|
||||
class User:
|
||||
def name():
|
||||
pass
|
||||
|
||||
def DM():
|
||||
pass
|
||||
|
||||
|
||||
class Communication: # better name
|
||||
def send(str):
|
||||
pass
|
||||
|
||||
def send_embed(embed):
|
||||
pass
|
||||
|
||||
def command():
|
||||
pass
|
||||
|
||||
def wait_for_message():
|
||||
pass
|
||||
|
||||
|
||||
class DM(Communication):
|
||||
pass
|
||||
|
||||
|
||||
class Room(Communication):
|
||||
pass
|
||||
|
||||
|
||||
class Context:
|
||||
def User():
|
||||
pass
|
||||
|
||||
def Communication():
|
||||
pass
|
||||
|
||||
def Message():
|
||||
pass
|
||||
|
||||
def mentions():
|
||||
pass
|
||||
|
||||
# args?
|
|
@ -1,54 +0,0 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
class Developer(commands.Cog):
|
||||
"""This class is intended only for the developer, mainly for testing purposes"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@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))
|
||||
|
||||
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()
|
||||
async def hello(self, ctx):
|
||||
await ctx.send(f"Hello {ctx.message.author.name} :wave:")
|
||||
|
||||
@commands.group(name="gg")
|
||||
async def group(self, ctx):
|
||||
pass
|
||||
|
||||
@group.command()
|
||||
@commands.check(is_dev)
|
||||
async def ping(self, ctx, i: int):
|
||||
await ctx.send("pong")
|
||||
await ctx.send(i + 1)
|
||||
|
||||
@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):
|
||||
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))
|
|
@ -1,49 +0,0 @@
|
|||
"""Has a single class: Game"""
|
||||
|
||||
# standard library imports
|
||||
from abc import ABC
|
||||
import asyncio
|
||||
|
||||
# discord imports
|
||||
import discord
|
||||
|
||||
# local imports
|
||||
from .players import Player
|
||||
|
||||
|
||||
class Game(ABC):
|
||||
"""This (abstract) class is a template for main-game-loop objects for games"""
|
||||
|
||||
@classmethod
|
||||
def name(cls):
|
||||
return "Game"
|
||||
|
||||
player_cls = Player
|
||||
|
||||
def __init__(self, bot, channel):
|
||||
self.bot = bot
|
||||
self.channel = channel
|
||||
self.running = False
|
||||
self.player_list = []
|
||||
|
||||
async def send(self, message):
|
||||
await self.channel.send(embed=discord.Embed(description=message, color=0x00ffff))
|
||||
|
||||
async def for_all_player(self, call):
|
||||
await asyncio.gather(*[call(p) for p in self.player_list])
|
||||
|
||||
async def set_players(self, msg):
|
||||
self.player_list = [await self.player_cls.make(mem, self) for mem in msg.mentions]
|
||||
await self.send(f"Players: {', '.join(p.name() for p in self.player_list)}") # send confirmation
|
||||
|
||||
def check(self):
|
||||
pass
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
def end(self):
|
||||
self.running = False
|
||||
|
||||
async def round(self):
|
||||
pass
|
|
@ -1,85 +0,0 @@
|
|||
"""Has a single class: Game_cog"""
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
class Game_cog(Send_message):
|
||||
"""This (abstract) class is are common function for the Game Cog's (setup-game, pre-game, in-game), mainly has checker functions"""
|
||||
|
||||
game_cls = Game
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
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:
|
||||
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
|
||||
|
||||
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")
|
||||
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()}")
|
||||
|
||||
async def reset(self, ctx):
|
||||
"""This function deletes the game instance for this channel"""
|
||||
if await self.setup_check(ctx):
|
||||
del self.game_instances[ctx.channel]
|
||||
|
||||
# TODO: better info 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_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: can't one access self instead of ctx.cog?
|
||||
def pre_game():
|
||||
async def predicate(ctx):
|
||||
return await ctx.cog.setup_check(ctx) and await ctx.cog.not_running_check(ctx)
|
||||
return commands.check(predicate)
|
||||
|
||||
async def 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
|
||||
|
||||
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()
|
||||
await self.send_friendly(ctx, "Game canceled")
|
|
@ -1,79 +0,0 @@
|
|||
"""Has a single class: Player"""
|
||||
|
||||
# discord imports
|
||||
import discord
|
||||
|
||||
|
||||
class Player:
|
||||
"""This (abstract) class is a template for player objects for games"""
|
||||
|
||||
@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]
|
|
@ -1,75 +0,0 @@
|
|||
"""Has a single class: Werewolf_Cog"""
|
||||
|
||||
# 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, 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()?
|
||||
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.command()
|
||||
async def setup(self, ctx):
|
||||
"""This function creates an game instance for this channel"""
|
||||
await super().setup(ctx)
|
||||
|
||||
@werewolf.command()
|
||||
async def reset(self, ctx):
|
||||
"""This function deletes the game instance for this channel"""
|
||||
await super().reset(ctx)
|
||||
|
||||
@werewolf.command()
|
||||
@Game_cog.pre_game()
|
||||
async def players(self, ctx):
|
||||
"""registers all mentioned players for the game"""
|
||||
await super().players(ctx)
|
||||
|
||||
@werewolf.command()
|
||||
@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()
|
||||
@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()
|
||||
@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()
|
||||
@Game_cog.in_game()
|
||||
async def stop(self, ctx):
|
||||
"""aborts the current round of werewolf"""
|
||||
await super().stop(ctx)
|
||||
|
||||
@werewolf.command()
|
||||
@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())
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Werewolf_cog(bot))
|
|
@ -1,228 +0,0 @@
|
|||
"""Has a single class: Werewolf_game"""
|
||||
|
||||
# standard library imports
|
||||
from random import shuffle
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
# discord imports
|
||||
import discord
|
||||
|
||||
# local imports
|
||||
from .roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role
|
||||
from .players import Werewolf_player, No_player
|
||||
from ..game import Game
|
||||
|
||||
|
||||
class Werewolf_game(Game):
|
||||
"""This class simulates One Night Ultimate Werewolf with the help of Werewolf_player and Werewolf_role"""
|
||||
|
||||
@classmethod
|
||||
def name(cls):
|
||||
return "One Night Ultimate Werewolf"
|
||||
|
||||
player_cls = Werewolf_player
|
||||
|
||||
def __init__(self, bot, channel):
|
||||
super().__init__(bot, channel)
|
||||
self.role_list = []
|
||||
self.discussion_time = 301 # seconds
|
||||
|
||||
async def set_roles(self, suggestions):
|
||||
self.role_list = [Role.match(r) for r in suggestions] # raises ValueError
|
||||
await self.send(f"Roles: {', '.join(r.name() for r in self.role_list)}") # send confirmation
|
||||
|
||||
async def set_time(self, minutes: int):
|
||||
self.discussion_time = minutes * 60 + 1
|
||||
await self.send(f"You have set the discussion time to {self.discussion_time//60} minutes") # send confirmation
|
||||
|
||||
def check(self):
|
||||
# TODO: min. player
|
||||
if not 1 <= len(self.player_list) <= 10:
|
||||
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]:
|
||||
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)
|
||||
# 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)
|
||||
role_obj.add_yourself()
|
||||
self.middle_card = [r(self) for r in self.role_list[-3:]]
|
||||
|
||||
async def start_night(self):
|
||||
await self.for_all_player(lambda p: p.send_normal(f"*The night has begun* {':full_moon:'*10}"))
|
||||
|
||||
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: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)
|
||||
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) # 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):
|
||||
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") # 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 ":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)
|
||||
|
||||
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()))
|
||||
finally:
|
||||
self.end()
|
|
@ -1,37 +0,0 @@
|
|||
"""Has a single class: Werewolf_player"""
|
||||
|
||||
# local import
|
||||
from ..players import Player
|
||||
|
||||
|
||||
class Werewolf_player(Player):
|
||||
"""This class is for simulating non-role-specific werewolf players"""
|
||||
|
||||
def set_role(self, role):
|
||||
self.day_role = self.night_role = role
|
||||
|
||||
def reset(self):
|
||||
self.tally = 0
|
||||
self.won = self.dead = False
|
||||
|
||||
def swap(self, player_B):
|
||||
self.day_role, player_B.day_role = player_B.day_role, self.day_role
|
||||
|
||||
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)]
|
||||
|
||||
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")
|
||||
|
||||
|
||||
# TODO: this seems hacky, find another approach
|
||||
class No_player(Werewolf_player):
|
||||
def name(self):
|
||||
return "no one"
|
|
@ -1,180 +0,0 @@
|
|||
import functools
|
||||
from fuzzywuzzy import fuzz
|
||||
from .players import No_player
|
||||
|
||||
|
||||
class Role:
|
||||
def __init__(self, game, player=No_player()):
|
||||
self.game = game
|
||||
self.player = player
|
||||
|
||||
def add_yourself(self):
|
||||
self.game.role[type(self)] = self
|
||||
|
||||
async def send_role_list(self, 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):
|
||||
return max(Role.__subclasses__(), key=lambda role_class: fuzz.ratio(message, role_class.name()))
|
||||
|
||||
@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()
|
||||
|
||||
def __str__(self):
|
||||
return self.name()
|
||||
|
||||
|
||||
class Doppelganger(Role):
|
||||
@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.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)
|
||||
elif self.copy_role in [Seer, Robber, Troublemaker, Drunk]:
|
||||
await self.copy_role.query(self)
|
||||
if self.copy_role in [Robber, Troublemaker, Drunk]:
|
||||
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:
|
||||
await self.copy_role.send_info(self)
|
||||
|
||||
def is_role(self, cls):
|
||||
return self.copy_role == cls
|
||||
|
||||
|
||||
class Werewolf(Role):
|
||||
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)
|
||||
else:
|
||||
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_info(f"A card in the middle is: {self.game.middle_card[self.choice].name()}")
|
||||
|
||||
|
||||
class Minion(Role):
|
||||
@Role.no_player
|
||||
async def send_info(self):
|
||||
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)
|
||||
|
||||
|
||||
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(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"You saw: {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())
|
||||
|
||||
@Role.no_player
|
||||
async 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.name()}")
|
||||
|
||||
|
||||
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())
|
||||
|
||||
@Role.no_player
|
||||
async 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"])
|
||||
|
||||
@Role.no_player
|
||||
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):
|
||||
@Role.no_player
|
||||
async def send_info(self):
|
||||
await self.player.send_info(f"You are: {self.player.day_role.name()}")
|
||||
|
||||
|
||||
class Villager(Role):
|
||||
pass
|
||||
|
||||
|
||||
class Tanner(Role):
|
||||
pass
|
||||
|
||||
|
||||
class Hunter(Role):
|
||||
pass
|
||||
|
||||
|
||||
class No_role(Role):
|
||||
pass
|
|
@ -1,18 +0,0 @@
|
|||
# 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)
|
|
@ -1,54 +0,0 @@
|
|||
"""
|
||||
This is the main module of the Discord Bot
|
||||
|
||||
Mainly loads the Cog's and starts the bot
|
||||
"""
|
||||
|
||||
__version__ = '0.4'
|
||||
__author__ = 'Bibin Muttappillil'
|
||||
|
||||
# standard library imports
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# discord imports
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
# 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('$b '))
|
||||
|
||||
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'package.{extension}')
|
||||
print(f'package.{extension} reloaded')
|
||||
|
||||
|
||||
# checker annotations
|
||||
# TODO: replace with discord.py error handling?
|
||||
|
||||
'''
|
||||
def error_handling(command):
|
||||
@functools.wraps(command)
|
||||
async def wrapper(ctx, *args, **kwargs):
|
||||
try:
|
||||
await command(ctx, *args, **kwargs)
|
||||
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
|
||||
'''
|
||||
|
||||
bot.run(TOKEN)
|
|
@ -0,0 +1,381 @@
|
|||
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)
|
|
@ -0,0 +1,499 @@
|
|||
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')
|
||||
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(p) + " 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)
|
Loading…
Reference in New Issue