start refactoring again (this time abstracting Discord API and removing dependencies of the game classe
This commit is contained in:
parent
14037bf5d8
commit
a4eaa141ac
|
@ -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
|
|
@ -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?
|
|
@ -13,7 +13,7 @@ class Developer(commands.Cog):
|
||||||
await self.bot.change_presence(status=discord.Status.online, activity=discord.Game('One Night Ultimate Werewolf'))
|
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))
|
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:
|
if ctx.author.id == 461892912821698562:
|
||||||
return True
|
return True
|
||||||
await ctx.send("This command is not for you!")
|
await ctx.send("This command is not for you!")
|
||||||
|
@ -41,15 +41,6 @@ class Developer(commands.Cog):
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.check(is_dev)
|
@commands.check(is_dev)
|
||||||
async def debug(self, ctx, *args):
|
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:
|
for emoji in ctx.guild.emojis:
|
||||||
await ctx.send(emoji)
|
await ctx.send(emoji)
|
||||||
print(emoji.id)
|
print(emoji.id)
|
||||||
|
|
|
@ -1,20 +1,49 @@
|
||||||
|
"""Has a single class: Game"""
|
||||||
|
|
||||||
|
# standard library imports
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# discord imports
|
||||||
|
import discord
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from .players import Player
|
||||||
|
|
||||||
|
|
||||||
class Game(ABC):
|
class Game(ABC):
|
||||||
|
"""This (abstract) class is a template for main-game-loop objects for games"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def name(cls):
|
def name(cls):
|
||||||
return "Game"
|
return "Game"
|
||||||
|
|
||||||
|
player_cls = Player
|
||||||
|
|
||||||
def __init__(self, bot, channel):
|
def __init__(self, bot, channel):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.running = False
|
self.running = False
|
||||||
self.player_list = []
|
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
|
pass
|
||||||
|
|
||||||
async def set_players(self):
|
def setup(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def end(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
async def round(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Has a single class: Game_cog"""
|
"""Has a single class: Game_cog"""
|
||||||
|
|
||||||
# standard library imports
|
# standard library imports
|
||||||
from typing import Dict, Type
|
from typing import Dict
|
||||||
|
|
||||||
# discord imports
|
# discord imports
|
||||||
import discord
|
import discord
|
||||||
|
@ -15,9 +15,10 @@ from .game import Game
|
||||||
class Game_cog(Send_message):
|
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"""
|
"""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.bot = bot
|
||||||
self.game_cls = game_cls
|
|
||||||
self.game_instances = {} # TODO: type hint? Dict[discord.TextChannel, self.game_cls]
|
self.game_instances = {} # TODO: type hint? Dict[discord.TextChannel, self.game_cls]
|
||||||
|
|
||||||
async def setup_check(self, ctx):
|
async def setup_check(self, ctx):
|
||||||
|
@ -45,7 +46,7 @@ class Game_cog(Send_message):
|
||||||
|
|
||||||
async def reset(self, ctx):
|
async def reset(self, ctx):
|
||||||
"""This function deletes the game instance for this channel"""
|
"""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]
|
del self.game_instances[ctx.channel]
|
||||||
|
|
||||||
# TODO: better info message
|
# TODO: better info message
|
||||||
|
@ -61,6 +62,7 @@ class Game_cog(Send_message):
|
||||||
embed.set_footer(text="Have fun!")
|
embed.set_footer(text="Have fun!")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
# TODO: can't one access self instead of ctx.cog?
|
||||||
def pre_game():
|
def pre_game():
|
||||||
async def predicate(ctx):
|
async def predicate(ctx):
|
||||||
return await ctx.cog.setup_check(ctx) and await ctx.cog.not_running_check(ctx)
|
return await ctx.cog.setup_check(ctx) and await ctx.cog.not_running_check(ctx)
|
||||||
|
|
|
@ -11,6 +11,8 @@ from .game import Werewolf_game
|
||||||
class Werewolf_cog(Game_cog, commands.Cog):
|
class Werewolf_cog(Game_cog, commands.Cog):
|
||||||
"""This singleton class is a Discord Cog for the interaction in the werewolf game"""
|
"""This singleton class is a Discord Cog for the interaction in the werewolf game"""
|
||||||
|
|
||||||
|
game_cls = Werewolf_game
|
||||||
|
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
async def werewolf(self, ctx):
|
async def werewolf(self, ctx):
|
||||||
# TODO: isn't there a better way to have a default subcommand? Maybe invoke super().info()?
|
# 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):
|
def setup(bot):
|
||||||
bot.add_cog(Werewolf_cog(bot, Werewolf_game))
|
bot.add_cog(Werewolf_cog(bot))
|
||||||
|
|
|
@ -1,54 +1,54 @@
|
||||||
|
"""Has a single class: Werewolf_game"""
|
||||||
|
|
||||||
|
# standard library imports
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
# discord imports
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
|
# local imports
|
||||||
from .roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role
|
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 .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
|
@classmethod
|
||||||
def name(cls):
|
def name(cls):
|
||||||
return "One Night Ultimate Werewolf"
|
return "One Night Ultimate Werewolf"
|
||||||
|
|
||||||
|
player_cls = Werewolf_player
|
||||||
|
|
||||||
def __init__(self, bot, channel):
|
def __init__(self, bot, channel):
|
||||||
self.running = False
|
super().__init__(bot, channel)
|
||||||
self.bot = bot
|
|
||||||
self.channel = channel
|
|
||||||
self.player_list = []
|
|
||||||
self.role_list = []
|
self.role_list = []
|
||||||
self.discussion_time = 301 # seconds
|
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):
|
async def set_roles(self, suggestions):
|
||||||
self.role_list = [Role.match(r) for r in suggestions] # raises ValueError
|
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
|
await self.send(f"Roles: {', '.join(r.name() for r in self.role_list)}") # send confirmation
|
||||||
|
|
||||||
async def set_time(self, msg):
|
async def set_time(self, minutes: int):
|
||||||
self.discussion_time = int(msg) * 60 + 1
|
self.discussion_time = minutes * 60 + 1
|
||||||
await self.send(f"You have set the discussion time to {self.discussion_time//60} minutes") # send confirmation
|
await self.send(f"You have set the discussion time to {self.discussion_time//60} minutes") # send confirmation
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
# TODO: min. player
|
# 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)}")
|
raise ValueError(f"Invalid number of players: {len(self.player_list)}")
|
||||||
if not len(self.role_list) == (len(self.player_list) + 3):
|
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")
|
raise ValueError(f"Invalid number of roles: {len(self.role_list)} with {len(self.player_list)} players")
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
# TODO: better...
|
||||||
self.role = dict()
|
self.role = dict()
|
||||||
# setting default value
|
# setting default value
|
||||||
for r in Role.__subclasses__():
|
for r in Role.__subclasses__():
|
||||||
|
# TODO: every role should be a list (not only werewolves, masons, ...)
|
||||||
if r == No_role:
|
if r == No_role:
|
||||||
continue
|
continue
|
||||||
if r in [Werewolf, Mason]:
|
if r in [Werewolf, Mason]:
|
||||||
|
@ -61,6 +61,7 @@ class Werewolf_game:
|
||||||
|
|
||||||
def distribute_roles(self):
|
def distribute_roles(self):
|
||||||
shuffle(self.role_list)
|
shuffle(self.role_list)
|
||||||
|
# TODO: use zip
|
||||||
for i in range(len(self.player_list)):
|
for i in range(len(self.player_list)):
|
||||||
role_obj = self.role_list[i](self, self.player_list[i])
|
role_obj = self.role_list[i](self, self.player_list[i])
|
||||||
self.player_list[i].set_role(role_obj)
|
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:]]
|
self.middle_card = [r(self) for r in self.role_list[-3:]]
|
||||||
|
|
||||||
async def start_night(self):
|
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):
|
async def send_role(self):
|
||||||
await self.for_all_player(lambda p: p.send_info(f"Your role: **{p.night_role.name()}**"))
|
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):
|
def remaining_time_string(self):
|
||||||
t = int(self.start_time + self.discussion_time - time.time())
|
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):
|
async def discussion_timer(self):
|
||||||
|
# TODO: better?
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
await self.send(f"You have {self.remaining_time_string()} to discuss")
|
await self.send(f"You have {self.remaining_time_string()} to discuss")
|
||||||
await asyncio.sleep(self.discussion_time / 2)
|
await asyncio.sleep(self.discussion_time / 2)
|
||||||
|
@ -123,8 +125,8 @@ class Werewolf_game:
|
||||||
p.vote.tally += 1
|
p.vote.tally += 1
|
||||||
|
|
||||||
def who_dead(self):
|
def who_dead(self):
|
||||||
maxi = max(c.tally for c in self.voting_list)
|
maxi = max(c.tally for c in self.voting_list) # most votes
|
||||||
dead = [p for p in self.player_list if p.tally >= maxi]
|
dead = [p for p in self.player_list if p.tally >= maxi] # who has most votes
|
||||||
for d in dead:
|
for d in dead:
|
||||||
d.dead = True
|
d.dead = True
|
||||||
if d.day_role.is_role(Hunter):
|
if d.day_role.is_role(Hunter):
|
||||||
|
@ -153,10 +155,8 @@ class Werewolf_game:
|
||||||
else:
|
else:
|
||||||
if tanner_dead:
|
if tanner_dead:
|
||||||
tanner_won = True
|
tanner_won = True
|
||||||
|
|
||||||
if werewolf_dead:
|
if werewolf_dead:
|
||||||
village_won = True
|
village_won = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if werewolf_dead:
|
if werewolf_dead:
|
||||||
village_won = True
|
village_won = True
|
||||||
|
@ -187,22 +187,29 @@ class Werewolf_game:
|
||||||
if village_won:
|
if village_won:
|
||||||
winnning.append("Village")
|
winnning.append("Village")
|
||||||
if tanner_won:
|
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:
|
if len(winnning) == 0:
|
||||||
winnning = ["No one"]
|
winnning = ["No one"]
|
||||||
|
|
||||||
|
# TODO: better emoji code?
|
||||||
|
space = "<:space:705863033871663185>"
|
||||||
embed = discord.Embed(title=f"{' and '.join(winnning)} won!", color=0x00ffff)
|
embed = discord.Embed(title=f"{' and '.join(winnning)} won!", color=0x00ffff)
|
||||||
for p in self.player_list:
|
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:"
|
dead_emoji = ":skull:" if p.dead else ":eyes:"
|
||||||
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)
|
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)
|
embed.add_field(name="Middle cards", value=', '.join(r.name() for r in self.middle_card), inline=False)
|
||||||
|
|
||||||
await self.channel.send(embed=embed)
|
await self.channel.send(embed=embed)
|
||||||
|
|
||||||
def end(self):
|
|
||||||
self.running = False
|
|
||||||
|
|
||||||
async def round(self):
|
async def round(self):
|
||||||
try:
|
try:
|
||||||
self.check()
|
self.check()
|
||||||
|
@ -217,6 +224,5 @@ class Werewolf_game:
|
||||||
await self.vote()
|
await self.vote()
|
||||||
self.tally()
|
self.tally()
|
||||||
await self.result(*self.who_won(self.who_dead()))
|
await self.result(*self.who_won(self.who_dead()))
|
||||||
await self.send("Round ended")
|
|
||||||
finally:
|
finally:
|
||||||
self.end()
|
self.end()
|
||||||
|
|
|
@ -4,7 +4,7 @@ This is the main module of the Discord Bot
|
||||||
Mainly loads the Cog's and starts the bot
|
Mainly loads the Cog's and starts the bot
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.3'
|
__version__ = '0.4'
|
||||||
__author__ = 'Bibin Muttappillil'
|
__author__ = 'Bibin Muttappillil'
|
||||||
|
|
||||||
# standard library imports
|
# standard library imports
|
||||||
|
@ -22,7 +22,7 @@ if TOKEN is None:
|
||||||
print("Missing discord token!")
|
print("Missing discord token!")
|
||||||
exit(1)
|
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.developer')
|
||||||
bot.load_extension('package.games.werewolf.cog')
|
bot.load_extension('package.games.werewolf.cog')
|
||||||
|
@ -32,6 +32,7 @@ bot.load_extension('package.games.werewolf.cog')
|
||||||
@commands.is_owner()
|
@commands.is_owner()
|
||||||
async def reload(ctx, extension):
|
async def reload(ctx, extension):
|
||||||
bot.reload_extension(f'package.{extension}')
|
bot.reload_extension(f'package.{extension}')
|
||||||
|
print(f'package.{extension} reloaded')
|
||||||
|
|
||||||
|
|
||||||
# checker annotations
|
# checker annotations
|
||||||
|
|
Loading…
Reference in New Issue