First Commit
This commit is contained in:
Executable
+330
@@ -0,0 +1,330 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import random
|
||||
import asyncio
|
||||
from collections import Counter
|
||||
|
||||
ROLES = ["Werewolf", "Seer", "Doctor", "Villager"]
|
||||
|
||||
ROLE_DESCRIPTIONS = {
|
||||
"Werewolf": "You are a **Werewolf**! Work with your fellow werewolves at night to eliminate villagers. Your goal is to outnumber the villagers.",
|
||||
"Seer": "You are the **Seer**! Each night, you may select a player to learn their true role.",
|
||||
"Doctor": "You are the **Doctor**! Each night, you may choose a player to protect from elimination.",
|
||||
"Villager": "You are a **Villager**! Try to find and vote out the werewolves during the day.",
|
||||
}
|
||||
|
||||
|
||||
class WerewolvesGame:
|
||||
def __init__(self, channel):
|
||||
self.channel = channel
|
||||
self.players = []
|
||||
self.started = False
|
||||
self.roles = {} # user_id: role
|
||||
self.phase = "lobby" # or "night", "day"
|
||||
self.role_threads = {} # role: thread
|
||||
self.votes = {}
|
||||
self.vote_targets = []
|
||||
self.alive = set()
|
||||
|
||||
def add_player(self, user):
|
||||
if user not in self.players:
|
||||
self.players.append(user)
|
||||
|
||||
def assign_roles(self):
|
||||
# For 6+ players: 2 werewolves, 1 seer, 1 doctor, rest villagers
|
||||
n = len(self.players)
|
||||
roles_list = ["Werewolf"] * 2 + ["Seer", "Doctor"]
|
||||
roles_list += ["Villager"] * (n - len(roles_list))
|
||||
random.shuffle(roles_list)
|
||||
self.roles = {player.id: role for player, role in zip(self.players, roles_list)}
|
||||
|
||||
def get_players_by_role(self, role):
|
||||
return [p for p in self.players if self.roles.get(p.id) == role]
|
||||
|
||||
def reset_votes(self):
|
||||
self.votes = {}
|
||||
self.vote_targets = []
|
||||
|
||||
def vote(self, voter, target):
|
||||
self.votes[voter.id] = target.id
|
||||
|
||||
def tally_votes(self):
|
||||
if not self.votes:
|
||||
return None
|
||||
count = Counter(self.votes.values())
|
||||
most_common = count.most_common(1)
|
||||
if most_common:
|
||||
return most_common[0][0] # user_id of voted out
|
||||
return None
|
||||
|
||||
|
||||
class Games(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.active_games = {} # channel_id: WerewolvesGame
|
||||
|
||||
@commands.command(name="joinwerewolves", hidden=True)
|
||||
async def join_werewolves(self, ctx):
|
||||
game = self.active_games.get(ctx.channel.id)
|
||||
if not game:
|
||||
game = WerewolvesGame(ctx.channel)
|
||||
self.active_games[ctx.channel.id] = game
|
||||
if game.started:
|
||||
await ctx.reply("Game already started!")
|
||||
return
|
||||
game.add_player(ctx.author)
|
||||
await ctx.reply(f"{ctx.author.display_name} joined the game!")
|
||||
|
||||
@commands.command(name="startwerewolves", hidden=True)
|
||||
async def start_werewolves(self, ctx):
|
||||
game = self.active_games.get(ctx.channel.id)
|
||||
if not game or len(game.players) < 2:
|
||||
await ctx.reply("Need at least 6 players to start!")
|
||||
return
|
||||
if game.started:
|
||||
await ctx.reply("Game already started!")
|
||||
return
|
||||
game.started = True
|
||||
game.assign_roles()
|
||||
|
||||
# Create threads for roles
|
||||
await self.create_role_threads(game)
|
||||
|
||||
# Announce roles in threads (description only once per thread)
|
||||
# Werewolf thread: mention all werewolves, send description once
|
||||
werewolves = game.get_players_by_role("Werewolf")
|
||||
if werewolves:
|
||||
thread = game.role_threads["Werewolf"]
|
||||
mentions = " ".join([p.mention for p in werewolves])
|
||||
desc = ROLE_DESCRIPTIONS["Werewolf"]
|
||||
await thread.send(f"{mentions}\nYou are **Werewolves**!\n\n{desc}")
|
||||
|
||||
# Seer thread
|
||||
seer = game.get_players_by_role("Seer")
|
||||
if seer:
|
||||
thread = game.role_threads["Seer"]
|
||||
desc = ROLE_DESCRIPTIONS["Seer"]
|
||||
await thread.send(f"{seer[0].mention}\nYou are the **Seer**!\n\n{desc}")
|
||||
|
||||
# Doctor thread
|
||||
doctor = game.get_players_by_role("Doctor")
|
||||
if doctor:
|
||||
thread = game.role_threads["Doctor"]
|
||||
desc = ROLE_DESCRIPTIONS["Doctor"]
|
||||
await thread.send(f"{doctor[0].mention}\nYou are the **Doctor**!\n\n{desc}")
|
||||
|
||||
await ctx.send(
|
||||
"Game started! Roles have been assigned in private threads.\nThe game cycle will begin in 30 seconds..."
|
||||
)
|
||||
|
||||
# Start the game cycle after 30 seconds
|
||||
await asyncio.sleep(30)
|
||||
await self.start_cycle(ctx)
|
||||
|
||||
async def create_role_threads(self, game: WerewolvesGame):
|
||||
# Werewolves share a thread, others get solo threads
|
||||
channel = game.channel
|
||||
werewolves = game.get_players_by_role("Werewolf")
|
||||
seer = game.get_players_by_role("Seer")
|
||||
doctor = game.get_players_by_role("Doctor")
|
||||
|
||||
# Create werewolf group thread
|
||||
if werewolves:
|
||||
thread = await channel.create_thread(
|
||||
name="Werewolves Actions",
|
||||
type=discord.ChannelType.private_thread,
|
||||
auto_archive_duration=60,
|
||||
reason="Werewolves game: Werewolves thread",
|
||||
)
|
||||
for member in werewolves:
|
||||
await thread.add_user(member)
|
||||
game.role_threads["Werewolf"] = thread
|
||||
|
||||
# Create seer thread
|
||||
if seer:
|
||||
thread = await channel.create_thread(
|
||||
name="Seer Actions",
|
||||
type=discord.ChannelType.private_thread,
|
||||
auto_archive_duration=60,
|
||||
reason="Werewolves game: Seer thread",
|
||||
)
|
||||
await thread.add_user(seer[0])
|
||||
game.role_threads["Seer"] = thread
|
||||
|
||||
# Create doctor thread
|
||||
if doctor:
|
||||
thread = await channel.create_thread(
|
||||
name="Doctor Actions",
|
||||
type=discord.ChannelType.private_thread,
|
||||
auto_archive_duration=60,
|
||||
reason="Werewolves game: Doctor thread",
|
||||
)
|
||||
await thread.add_user(doctor[0])
|
||||
game.role_threads["Doctor"] = thread
|
||||
|
||||
@commands.command(name="playwerewolves")
|
||||
@commands.guild_only()
|
||||
async def play_werewolves(self, ctx):
|
||||
"""Create a private game room for Werewolves and start the game setup."""
|
||||
guild = ctx.guild
|
||||
overwrites = {
|
||||
guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
||||
ctx.author: discord.PermissionOverwrite(
|
||||
read_messages=True, send_messages=True
|
||||
),
|
||||
}
|
||||
category = ctx.channel.category
|
||||
channel_name = f"werewolves-room-{ctx.author.display_name}".replace(
|
||||
" ", "-"
|
||||
).lower()
|
||||
channel = await guild.create_text_channel(
|
||||
name=channel_name,
|
||||
overwrites=overwrites,
|
||||
category=category,
|
||||
reason="Private Werewolves game room",
|
||||
)
|
||||
await channel.send(
|
||||
f"{ctx.author.mention} Your private **Werewolves** game room has been created!\n"
|
||||
f"Use `py addwerewolves <username>` in this channel to add others.\n"
|
||||
f"Use `py joinwerewolves` in this channel to join the game.\n"
|
||||
f"Once enough players have joined, use `py startwerewolves` to start the game."
|
||||
)
|
||||
# Optionally, auto-join the creator to the game
|
||||
game = WerewolvesGame(channel)
|
||||
game.add_player(ctx.author)
|
||||
self.active_games[channel.id] = game
|
||||
|
||||
@commands.command(name="addwerewolves", hidden=True)
|
||||
@commands.guild_only()
|
||||
async def add_werewolves(
|
||||
self,
|
||||
ctx,
|
||||
member: discord.Member | None = None,
|
||||
*,
|
||||
identifier: str | None = None,
|
||||
):
|
||||
"""Add a user to the current Werewolves game room by username (case-insensitive, not mention)."""
|
||||
game = self.active_games.get(ctx.channel.id)
|
||||
if not game:
|
||||
await ctx.reply("This is not a Werewolves game room.")
|
||||
return
|
||||
|
||||
# Only allow the creator (first player) to add others
|
||||
if ctx.author != game.players[0]:
|
||||
await ctx.reply("Only the game creator can add players to the room.")
|
||||
return
|
||||
|
||||
# Find member by username (case-insensitive)
|
||||
if member is None and identifier:
|
||||
try:
|
||||
member = ctx.guild.get_member(int(identifier))
|
||||
except ValueError:
|
||||
member = discord.utils.find(
|
||||
lambda m: m.name.lower() == identifier.lower()
|
||||
or m.display_name.lower() == identifier.lower(),
|
||||
ctx.guild.members,
|
||||
)
|
||||
if not member:
|
||||
await ctx.reply(f"User '{member}' not found in this server.")
|
||||
return
|
||||
|
||||
await ctx.channel.set_permissions(
|
||||
member,
|
||||
read_messages=True,
|
||||
send_messages=True,
|
||||
)
|
||||
await ctx.send(
|
||||
f"{member.mention} has been added to the game room! They can now join the game with `py joinwerewolves`."
|
||||
)
|
||||
|
||||
@commands.command(name="startcycle", hidden=True)
|
||||
async def start_cycle(self, ctx):
|
||||
"""Start the Werewolves game cycle (night/day loop)."""
|
||||
game = self.active_games.get(ctx.channel.id)
|
||||
if not game or not game.started:
|
||||
await ctx.send("No active game to start the cycle.")
|
||||
return
|
||||
|
||||
await ctx.send("The game cycle is starting!")
|
||||
game.phase = "night"
|
||||
game.alive = set(game.players)
|
||||
game.reset_votes()
|
||||
|
||||
while True:
|
||||
# NIGHT PHASE
|
||||
await ctx.send(
|
||||
"🌙 **Night falls!** Werewolves, Seer, and Doctor, check your threads."
|
||||
)
|
||||
await asyncio.sleep(10) # Replace with your night action logic and timing
|
||||
|
||||
# DAY PHASE
|
||||
await ctx.send(
|
||||
"☀️ **Day breaks!** Discuss and vote to eliminate a player. Use `py vote <username>`."
|
||||
)
|
||||
game.reset_votes()
|
||||
await self.day_voting(ctx, game)
|
||||
voted_out_id = game.tally_votes()
|
||||
if voted_out_id:
|
||||
voted_out = discord.utils.get(ctx.guild.members, id=voted_out_id)
|
||||
if voted_out in game.alive:
|
||||
game.alive.remove(voted_out)
|
||||
await ctx.send(f"{voted_out.mention} has been eliminated!")
|
||||
else:
|
||||
await ctx.send("No one was eliminated today.")
|
||||
|
||||
# Check win condition (example: only werewolves or villagers left)
|
||||
werewolves = [p for p in game.alive if game.roles.get(p.id) == "Werewolf"]
|
||||
villagers = [p for p in game.alive if game.roles.get(p.id) != "Werewolf"]
|
||||
if not werewolves:
|
||||
await ctx.send("Villagers win! 🎉")
|
||||
break
|
||||
if len(werewolves) >= len(villagers):
|
||||
await ctx.send("Werewolves win! 🐺")
|
||||
break
|
||||
|
||||
await asyncio.sleep(5) # Short pause before next night
|
||||
|
||||
@commands.command(name="vote", hidden=True)
|
||||
async def vote(
|
||||
self,
|
||||
ctx,
|
||||
member: discord.Member | None = None,
|
||||
*,
|
||||
identifier: str | None = None,
|
||||
):
|
||||
"""Vote to eliminate a player during the day phase."""
|
||||
game = self.active_games.get(ctx.channel.id)
|
||||
if not game or ctx.author not in game.alive:
|
||||
await ctx.reply("You are not in the game or not alive.")
|
||||
return
|
||||
|
||||
# Find member by username (case-insensitive)
|
||||
if member is None and identifier:
|
||||
try:
|
||||
member = ctx.guild.get_member(int(identifier))
|
||||
except ValueError:
|
||||
member = discord.utils.find(
|
||||
lambda m: m.name.lower() == identifier.lower()
|
||||
or m.display_name.lower() == identifier.lower(),
|
||||
ctx.guild.members,
|
||||
)
|
||||
if not member:
|
||||
await ctx.reply(f"User '{member}' not found in this server.")
|
||||
return
|
||||
|
||||
if not member or member not in game.alive:
|
||||
await ctx.reply("That player is not alive or not found.")
|
||||
return
|
||||
game.vote(ctx.author, member)
|
||||
await ctx.send(
|
||||
f"{ctx.author.display_name} voted to eliminate {member.display_name}."
|
||||
)
|
||||
|
||||
async def day_voting(self, ctx, game, timeout=30):
|
||||
"""Wait for votes during the day phase."""
|
||||
await ctx.send(f"You have {timeout} seconds to vote.")
|
||||
await asyncio.sleep(timeout)
|
||||
|
||||
|
||||
async def setup(client):
|
||||
await client.add_cog(Games(client))
|
||||
Reference in New Issue
Block a user