From e59e3145316a682788ec365e39ba382dfc4758c9 Mon Sep 17 00:00:00 2001
From: aikaterna <20862007+aikaterna@users.noreply.github.com>
Date: Mon, 17 Dec 2018 11:02:58 -0800
Subject: [PATCH] [V3 CardsAgainstHumanity] Initial commit
---
cah/cah.py | 1839 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1839 insertions(+)
create mode 100644 cah/cah.py
diff --git a/cah/cah.py b/cah/cah.py
new file mode 100644
index 0000000..b369b1b
--- /dev/null
+++ b/cah/cah.py
@@ -0,0 +1,1839 @@
+import asyncio
+import discord
+import html
+import json
+import random
+import time
+from random import shuffle
+from redbot.core import commands
+from redbot.core.data_manager import cog_data_path
+
+
+BaseCog = getattr(commands, "Cog", object)
+
+
+class CardsAgainstHumanity(BaseCog):
+ def __init__(self, bot, file=None):
+ self.bot = bot
+ self.games = []
+ self.maxBots = 5 # Max number of bots that can be added to a game - don't count toward max players
+ self.maxPlayers = 10 # Max players for ranjom joins
+ self.maxDeadTime = 3600 # Allow an hour of dead time before killing a game
+ self.checkTime = 300 # 5 minutes between dead time checks
+ self.winAfter = 10 # 10 wins for the game
+ self.botWaitMin = 5 # Minimum number of seconds before the bot makes a decision (default 5)
+ self.botWaitMax = 30 # Max number of seconds before a bot makes a decision (default 30)
+ self.userTimeout = 500 # 5 minutes to timeout
+ self.utCheck = 30 # Check timeout every 30 seconds
+ self.utWarn = 60 # Warn the user if they have 60 seconds or less before being kicked
+ self.charset = "1234567890"
+ self.botName = "Rando Cardrissian"
+ self.minMembers = 3
+
+ file_path = str(cog_data_path(self)) + "/deck.json"
+ f = open(file_path, "r")
+ filedata = f.read()
+ f.close()
+ self.deck = json.loads(filedata)
+
+ self.bot.loop.create_task(self.checkDead())
+ self.bot.loop.create_task(self.checkUserTimeout())
+
+ def cleanJson(self, json):
+ json = html.unescape(json)
+ # Clean out html formatting
+ json = json.replace("_", "[blank]")
+ json = json.replace("
", "\n")
+ json = json.replace("
", "\n")
+ json = json.replace("", "*")
+ json = json.replace("", "*")
+ return json
+
+ def displayname(self, member: discord.Member):
+ # A helper function to return the member's display name
+ nick = name = None
+ try:
+ nick = member.nick
+ except AttributeError:
+ pass
+ try:
+ name = member.name
+ except AttributeError:
+ pass
+ if nick:
+ return nick
+ if name:
+ return name
+ return None
+
+ def memberforname(self, name, server):
+ # Check nick first - then name
+ for member in server.members:
+ if member.nick:
+ if member.nick.lower() == name.lower():
+ return member
+ for member in server.members:
+ if member.name.lower() == name.lower():
+ return member
+ # No member yet - try ID
+ memID = "".join(list(filter(str.isdigit, name)))
+ newMem = memberForID(memID, server)
+ if newMem:
+ return newMem
+ return None
+
+ def getreadabletimebetween(self, first, last):
+ # A helper function to make a readable string between two times
+ timeBetween = int(last - first)
+ weeks = int(timeBetween / 604800)
+ days = int((timeBetween - (weeks * 604800)) / 86400)
+ hours = int((timeBetween - (days * 86400 + weeks * 604800)) / 3600)
+ minutes = int((timeBetween - (hours * 3600 + days * 86400 + weeks * 604800)) / 60)
+ seconds = int(timeBetween - (minutes * 60 + hours * 3600 + days * 86400 + weeks * 604800))
+ msg = ""
+
+ if weeks > 0:
+ if weeks == 1:
+ msg = "{}{} week, ".format(msg, str(weeks))
+ else:
+ msg = "{}{} weeks, ".format(msg, str(weeks))
+ if days > 0:
+ if days == 1:
+ msg = "{}{} day, ".format(msg, str(days))
+ else:
+ msg = "{}{} days, ".format(msg, str(days))
+ if hours > 0:
+ if hours == 1:
+ msg = "{}{} hour, ".format(msg, str(hours))
+ else:
+ msg = "{}{} hours, ".format(msg, str(hours))
+ if minutes > 0:
+ if minutes == 1:
+ msg = "{}{} minute, ".format(msg, str(minutes))
+ else:
+ msg = "{}{} minutes, ".format(msg, str(minutes))
+ if seconds > 0:
+ if seconds == 1:
+ msg = "{}{} second, ".format(msg, str(seconds))
+ else:
+ msg = "{}{} seconds, ".format(msg, str(seconds))
+
+ if not msg:
+ return "0 seconds"
+ else:
+ return msg[:-2]
+
+ async def checkUserTimeout(self):
+ while True:
+ # Wait first - then check
+ await asyncio.sleep(self.utCheck)
+ for game in self.games:
+ if not game["Timeout"]:
+ continue
+ if len(game["Members"]) >= self.minMembers:
+ # Game is started
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ if game["Judging"]:
+ if not member == game["Members"][game["Judge"]]:
+ # Not the judge - don't hold against the user
+ member["Time"] = int(time.time())
+ continue
+ else:
+ # Not judging
+ if member == game["Members"][game["Judge"]]:
+ # The judge - don't hold that against them
+ member["Time"] = int(time.time())
+ continue
+ currentTime = int(time.time())
+ userTime = member["Time"]
+ downTime = currentTime - userTime
+ # Check if downTime results in a kick
+ if downTime >= self.userTimeout:
+ # You gettin kicked, son.
+ await self.removeMember(member["User"])
+ self.checkGame(game)
+ continue
+ # Check if downTime is in warning time
+ if downTime >= (self.userTimeout - self.utWarn):
+ # Check if we're at warning phase
+ if self.userTimeout - downTime >= (self.utWarn - self.utCheck):
+ kickTime = self.userTimeout - downTime
+ if kickTime % self.utCheck:
+ # Kick time isn't exact time - round out to the next loop
+ kickTime = kickTime - (kickTime % self.utCheck) + self.utCheck
+ # Warning time!
+ timeString = self.getreadabletimebetween(0, kickTime)
+ msg = "**WARNING** - You will be kicked from the game if you do not make a move in *{}!*".format(
+ timeString
+ )
+ await member["User"].send(msg)
+ else:
+ for member in game["Members"]:
+ # Reset timer
+ member["Time"] = int(time.time())
+
+ async def checkDead(self):
+ while True:
+ # Wait first - then check
+ await asyncio.sleep(self.checkTime)
+ for game in self.games:
+ gameTime = game["Time"]
+ currentTime = int(time.time())
+ timeRemain = currentTime - gameTime
+ if timeRemain > self.maxDeadTime:
+ # Game is dead - quit it and alert members
+ for member in game["Members"]:
+ if member["IsBot"]:
+ # Clear pending tasks and set to None
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ task.cancel()
+ member["Task"] = None
+ continue
+ msg = "Game id: *{}* has been closed due to inactivity.".format(game["ID"])
+ await member["User"].send(msg)
+
+ # Set running to false
+ game["Running"] = False
+ self.games.remove(game)
+
+ async def checkPM(self, message):
+ # Checks if we're talking in PM, and if not - outputs an error
+ if isinstance(message.channel, discord.abc.PrivateChannel):
+ # PM
+ return True
+ else:
+ # Not in PM
+ await message.channel.send("Cards Against Humanity commands must be run in PM.")
+ return False
+
+ def randomID(self, length=8):
+ # Create a random id that doesn't already exist
+ while True:
+ # Repeat until found
+ newID = "".join(random.choice(self.charset) for i in range(length))
+ exists = False
+ for game in self.games:
+ if game["ID"] == newID:
+ exists = True
+ break
+ if not exists:
+ break
+ return newID
+
+ def randomBotID(self, game, length=4):
+ # Returns a random id for a bot that doesn't already exist
+ while True:
+ # Repeat until found
+ newID = "".join(random.choice(self.charset) for i in range(length))
+ exists = False
+ for member in game["Members"]:
+ if member["ID"] == newID:
+ exists = True
+ break
+ if not exists:
+ break
+ return newID
+
+ async def userGame(self, user):
+ # Returns the game the user is currently in
+ if not type(user) is int:
+ # Assume it's a discord.Member/User
+ user = user.id
+
+ for game in self.games:
+ for member in game["Members"]:
+ if member["ID"] == user:
+ # Found our user
+ return game
+ return None
+
+ def gameForID(self, id):
+ # Returns the game with the passed id
+ for game in self.games:
+ if game["ID"] == id:
+ return game
+ return None
+
+ async def removeMember(self, user, game=None):
+ if not type(user) is int:
+ # Assume it's a discord.Member/User
+ user = user.id
+ outcome = False
+ removed = None
+ if not game:
+ game = await self.userGame(user)
+ if game:
+ for member in game["Members"]:
+ if member["ID"] == user:
+ removed = member
+ outcome = True
+ judgeChanged = False
+ # Reset judging flag to retrigger actions
+ game["Judging"] = False
+ # Get current Judge - only if game has started
+ if len(game["Members"]) >= self.minMembers:
+ judge = game["Members"][game["Judge"]]
+ game["Members"].remove(member)
+ # Check if we're removing the current judge
+ if judge == member:
+ # Judge will change
+ judgeChanged = True
+ # Find out if our member was the last in line
+ if game["Judge"] >= len(game["Members"]):
+ game["Judge"] = 0
+ # Reset judge var
+ judge = game["Members"][game["Judge"]]
+ else:
+ # Judge didn't change - so let's reset judge index
+ index = game["Members"].index(judge)
+ game["Judge"] = index
+ else:
+ judge = None
+ # Just remove the member
+ game["Members"].remove(member)
+
+ if member["Creator"]:
+ # We're losing the game creator - pick a new one
+ for newCreator in game["Members"]:
+ if not newCreator["IsBot"]:
+ newCreator["Creator"] = True
+ await newCreator["User"].send(
+ "The creator of this game left. **YOU** are now the creator."
+ )
+ break
+
+ # Remove submissions
+ for sub in game["Submitted"]:
+ # Remove deleted member and new judge's submissions
+ if sub["By"] == member or sub["By"] == judge:
+ # Found it!
+ game["Submitted"].remove(sub)
+ break
+ if member["IsBot"]:
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ task.cancel()
+ member["Task"] = None
+ else:
+ msg = "**You were removed from game id:** ***{}.***".format(game["ID"])
+ await member["User"].send(msg)
+ # Removed, no need to finish the loop
+ break
+ if not outcome:
+ return outcome
+ # We removed someone - let's tell the world
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ if removed["IsBot"]:
+ msg = "***{} ({})*** **left the game - reorganizing...**".format(
+ self.botName, removed["ID"]
+ )
+ else:
+ msg = "***{}*** **left the game - reorganizing...**".format(
+ self.displayname(removed["User"])
+ )
+ # Check if the judge changed
+ if judgeChanged:
+ # Judge changed
+ newJudge = game["Members"][game["Judge"]]
+ if newJudge["IsBot"]:
+ msg += "\n\n***{} ({})*** **is now judging!**".format(
+ self.botName, newJudge["ID"]
+ )
+ # Schedule judging task
+ else:
+ if newJudge == member:
+ msg += "\n\n***YOU*** **are now judging!**"
+ else:
+ msg += "\n\n***{}*** **is now judging!**".format(
+ self.displayname(newJudge["User"])
+ )
+ await member["User"].send(msg)
+ return game
+
+ def checkGame(self, game):
+ for member in game["Members"]:
+ if not member["IsBot"]:
+ return True
+ # If we got here - only bots, or empty game
+ # Kill all bots' loops
+ for member in game["Members"]:
+ if member["IsBot"]:
+ # Clear pending tasks and set to None
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ task.cancel()
+ member["Task"] = None
+ # Set running to false
+ game["Running"] = False
+ self.games.remove(game)
+ return False
+
+ async def typing(self, game, typeTime=5):
+ # Allows us to show the bot typing
+ waitTime = random.randint(self.botWaitMin, self.botWaitMax)
+ preType = waitTime - typeTime
+ if preType > 0:
+ await asyncio.sleep(preType)
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ # Show that we're typing
+ await self.bot.send_typing(member["User"])
+ await asyncio.sleep(0.1)
+ await asyncio.sleep(typeTime)
+ else:
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ # Show that we're typing
+ await self.bot.send_typing(member["User"])
+ await asyncio.sleep(0.1)
+ await asyncio.sleep(waitTime)
+
+ async def botPick(self, ctx, bot, game):
+ # Has the bot pick their card
+ blackNum = game["BlackCard"]["Pick"]
+ if blackNum == 1:
+ cardSpeak = "card"
+ else:
+ cardSpeak = "cards"
+ i = 0
+ cards = []
+ while i < blackNum:
+ randCard = random.randint(0, len(bot["Hand"]) - 1)
+ cards.append(bot["Hand"].pop(randCard)["Text"])
+ i += 1
+
+ await self.typing(game)
+
+ # Make sure we haven't laid any cards
+ if bot["Laid"] == False and game["Judging"] == False:
+ newSubmission = {"By": bot, "Cards": cards}
+ game["Submitted"].append(newSubmission)
+ # Shuffle cards
+ shuffle(game["Submitted"])
+ bot["Laid"] = True
+ game["Time"] = currentTime = int(time.time())
+ await self.checkSubmissions(ctx, game, bot)
+
+ async def botPickWin(self, ctx, game):
+ totalUsers = len(game["Members"]) - 1
+ submitted = len(game["Submitted"])
+ if submitted >= totalUsers:
+ # Judge is a bot - and all cards are in!
+ await self.typing(game)
+ # Pick a winner
+ winner = random.randint(0, totalUsers - 1)
+ await self.winningCard(ctx, game, winner)
+
+ async def checkSubmissions(self, ctx, game, user=None):
+ totalUsers = len(game["Members"]) - 1
+ submitted = len(game["Submitted"])
+ for member in game["Members"]:
+ msg = ""
+ # Is the game running?
+ if len(game["Members"]) < self.minMembers:
+ if member["IsBot"]:
+ # Clear pending tasks and set to None
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ # Task isn't finished - we're on a new hand, cancel it
+ task.cancel()
+ member["Task"] = None
+ continue
+ # not enough members - send the embed
+ prefix = await self.bot.db.prefix()
+ stat_embed = discord.Embed(color=discord.Color.red())
+ stat_embed.set_author(
+ name="Not enough players to continue! ({}/{})".format(
+ len(game["Members"]), self.minMembers
+ )
+ )
+ stat_embed.set_footer(
+ text="Have other users join with: {}joincah {}".format(prefix[0], game["ID"])
+ )
+ await member["User"].send(embed=stat_embed)
+ continue
+ if member["IsBot"] == True:
+ continue
+ # Check if we have a user
+ if user:
+ blackNum = game["BlackCard"]["Pick"]
+ if blackNum == 1:
+ card = "card"
+ else:
+ card = "cards"
+ if user["IsBot"]:
+ msg = "*{} ({})* submitted their {}! ".format(self.botName, user["ID"], card)
+ else:
+ if not member == user:
+ # Don't say this to the submitting user
+ msg = "*{}* submitted their {}! ".format(
+ self.displayname(user["User"]), card
+ )
+ if submitted < totalUsers:
+ msg += "{}/{} cards submitted...".format(submitted, totalUsers)
+ if len(msg):
+ # We have something to say
+ await member["User"].send(msg)
+
+ async def checkCards(self, ctx, game):
+ while True:
+ if not game["Running"]:
+ break
+ # wait for 1 second
+ await asyncio.sleep(1)
+ # Check for all cards
+ if len(game["Members"]) < self.minMembers:
+ # Not enough members
+ continue
+ # Enough members - let's check if we're judging
+ if game["Judging"]:
+ continue
+ # Enough members, and not judging - let's check cards
+ totalUsers = len(game["Members"]) - 1
+ submitted = len(game["Submitted"])
+ if submitted >= totalUsers:
+ game["Judging"] = True
+ # We have enough cards
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ msg = "All cards have been submitted!"
+ # if
+ await member["User"].send(msg)
+ await self.showOptions(ctx, member["User"])
+
+ # Check if a bot is the judge
+ judge = game["Members"][game["Judge"]]
+ if not judge["IsBot"]:
+ continue
+ # task = self.bot.loop.create_task(self.botPickWin(ctx, game))
+ task = asyncio.ensure_future(self.botPickWin(ctx, game))
+ judge["Task"] = task
+
+ async def winningCard(self, ctx, game, card):
+ # Let's pick our card and alert everyone
+ winner = game["Submitted"][card]
+ if winner["By"]["IsBot"]:
+ winnerName = "{} ({})".format(self.botName, winner["By"]["ID"])
+ winner["By"]["Points"] += 1
+ winner["By"]["Won"].append(game["BlackCard"]["Text"])
+ else:
+ winnerName = self.displayname(winner["By"]["User"])
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ stat_embed = discord.Embed(color=discord.Color.gold())
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(game["ID"]))
+ index = game["Members"].index(member)
+ if index == game["Judge"]:
+ stat_embed.set_author(name="You picked {}'s card!".format(winnerName))
+ elif member == winner["By"]:
+ stat_embed.set_author(name="YOU WON!!")
+ member["Points"] += 1
+ member["Won"].append(game["BlackCard"]["Text"])
+ else:
+ stat_embed.set_author(name="{} won!".format(winnerName))
+ if len(winner["Cards"]) == 1:
+ msg = "The **Winning** card was:\n\n{}".format(
+ "{}".format(" - ".join(winner["Cards"]))
+ )
+ else:
+ msg = "The **Winning** cards were:\n\n{}".format(
+ "{}".format(" - ".join(winner["Cards"]))
+ )
+ await member["User"].send(embed=stat_embed)
+ await member["User"].send(msg)
+ await asyncio.sleep(0.1)
+
+ # await self.nextPlay(ctx, game)
+
+ # Start the game loop
+ event = game["NextHand"]
+ self.bot.loop.call_soon_threadsafe(event.set)
+ game["Time"] = currentTime = int(time.time())
+
+ async def gameCheckLoop(self, ctx, game):
+ task = game["NextHand"]
+ while True:
+ if not game["Running"]:
+ break
+ # Clear the pending task
+ task.clear()
+ # Queue up the next hand
+ await self.nextPlay(ctx, game)
+ # Wait until our next clear
+ await task.wait()
+
+ async def messagePlayers(self, ctx, message, game, judge=False):
+ # Messages all the users on in a game
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ # Not bots
+ if member is game["Members"][game["Judge"]]:
+ # Is the judge
+ if judge:
+ await member["User"].send(message)
+ else:
+ # Not the judge
+ await member["User"].send(message)
+
+ ################################################
+
+ async def showPlay(self, ctx, user):
+ # Creates an embed and displays the current game stats
+ stat_embed = discord.Embed(color=discord.Color.blue())
+ game = await self.userGame(user)
+ if not game:
+ return
+ # Get the judge's name
+ if game["Members"][game["Judge"]]["User"] == user:
+ judge = "**YOU** are"
+ else:
+ if game["Members"][game["Judge"]]["IsBot"]:
+ # Bot
+ judge = "*{} ({})* is".format(self.botName, game["Members"][game["Judge"]]["ID"])
+ else:
+ judge = "*{}* is".format(self.displayname(game["Members"][game["Judge"]]["User"]))
+
+ # Get the Black Card
+ try:
+ blackCard = game["BlackCard"]["Text"]
+ blackNum = game["BlackCard"]["Pick"]
+ except Exception:
+ blackCard = "None."
+ blackNum = 0
+
+ msg = "{} the judge.\n\n".format(judge)
+ msg += "__Black Card:__\n\n**{}**\n\n".format(blackCard)
+
+ totalUsers = len(game["Members"]) - 1
+ submitted = len(game["Submitted"])
+ if len(game["Members"]) >= self.minMembers:
+ if submitted < totalUsers:
+ msg += "{}/{} cards submitted...".format(submitted, totalUsers)
+ else:
+ msg += "All cards have been submitted!"
+ await self.showOptions(ctx, user)
+ return
+ prefix = await self.bot.db.prefix()
+ if not judge == "**YOU** are":
+ # Judge doesn't need to lay a card
+ if blackNum == 1:
+ # Singular
+ msg += "\n\nLay a card with `{}lay [card number]`".format(prefix[0])
+ elif blackNum > 1:
+ # Plural
+ msg += "\n\nLay **{} cards** with `{}lay [card numbers separated by commas (1,2,3)]`".format(
+ blackNum, prefix[0]
+ )
+
+ stat_embed.set_author(name="Current Play")
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(game["ID"]))
+ await user.send(embed=stat_embed)
+ await user.send(msg)
+
+ async def showHand(self, ctx, user):
+ # Shows the user's hand in an embed
+ stat_embed = discord.Embed(color=discord.Color.green())
+ game = await self.userGame(user)
+ if not game:
+ return
+ i = 0
+ msg = ""
+ points = "? points"
+ for member in game["Members"]:
+ if member["ID"] == user.id:
+ # Got our user
+ if member["Points"] == 1:
+ points = "1 point"
+ else:
+ points = "{} points".format(member["Points"])
+ for card in member["Hand"]:
+ i += 1
+ msg += "{}. {}\n".format(i, card["Text"])
+
+ try:
+ blackCard = "**{}**".format(game["BlackCard"]["Text"])
+ except Exception:
+ blackCard = "**None.**"
+ stat_embed.set_author(name="Your Hand - {}".format(points))
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(game["ID"]))
+ await user.send(embed=stat_embed)
+ await user.send(msg)
+
+ async def showOptions(self, ctx, user):
+ # Shows the judgement options
+ stat_embed = discord.Embed(color=discord.Color.orange())
+ game = await self.userGame(user)
+ if not game:
+ return
+ # Add title
+ stat_embed.set_author(name="JUDGEMENT TIME!!")
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(game["ID"]))
+ await user.send(embed=stat_embed)
+
+ if game["Members"][game["Judge"]]["User"] == user:
+ judge = "**YOU** are"
+ else:
+ if game["Members"][game["Judge"]]["IsBot"]:
+ # Bot
+ judge = "*{} ({})* is".format(self.botName, game["Members"][game["Judge"]]["ID"])
+ else:
+ judge = "*{}* is".format(self.displayname(game["Members"][game["Judge"]]["User"]))
+ blackCard = game["BlackCard"]["Text"]
+
+ msg = "{} judging.\n\n".format(judge)
+ msg += "__Black Card:__\n\n**{}**\n\n".format(blackCard)
+ msg += "__Submitted White Cards:__\n\n"
+
+ i = 0
+ for sub in game["Submitted"]:
+ i += 1
+ msg += "{}. {}\n".format(i, " - ".join(sub["Cards"]))
+ if judge == "**YOU** are":
+ prefix = await self.bot.db.prefix()
+ msg += "\nPick a winner with `{}pick [submission number]`.".format(prefix[0])
+ await user.send(msg)
+
+ async def drawCard(self, game):
+ # Draws a random unused card and shuffles the deck if needed
+ totalDiscard = len(game["Discard"])
+ for member in game["Members"]:
+ totalDiscard += len(member["Hand"])
+ if totalDiscard >= len(self.deck["whiteCards"]):
+ # Tell everyone the cards were shuffled
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ user = member["User"]
+ await user.send("Shuffling white cards...")
+ # Shuffle the cards
+ self.shuffle(game)
+ while True:
+ # Random grab a unique card
+ index = random.randint(0, len(self.deck["whiteCards"]) - 1)
+ if not index in game["Discard"]:
+ game["Discard"].append(index)
+ text = self.deck["whiteCards"][index]
+ text = self.cleanJson(text)
+ card = {"Index": index, "Text": text}
+ return card
+
+ def shuffle(self, game):
+ # Adds discards back into the deck
+ game["Discard"] = []
+ for member in game["Members"]:
+ for card in member["Hand"]:
+ game["Discard"].append(card["Index"])
+
+ async def drawCards(self, user, cards=10):
+ if not type(user) is int:
+ # Assume it's a discord.Member/User
+ user = user.id
+ # fills the user's hand up to number of cards
+ game = await self.userGame(user)
+ for member in game["Members"]:
+ if member["ID"] == user:
+ # Found our user - let's draw cards
+ i = len(member["Hand"])
+ while i < cards:
+ # Draw unique cards until we fill our hand
+ newCard = await self.drawCard(game)
+ member["Hand"].append(newCard)
+ i += 1
+
+ async def drawBCard(self, game):
+ # Draws a random black card
+ totalDiscard = len(game["BDiscard"])
+ if totalDiscard >= len(self.deck["blackCards"]):
+ # Tell everyone the cards were shuffled
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ user = member["User"]
+ await user.send("Shuffling black cards...")
+ # Shuffle the cards
+ game["BDiscard"] = []
+ while True:
+ # Random grab a unique card
+ index = random.randint(0, len(self.deck["blackCards"]) - 1)
+ if not index in game["BDiscard"]:
+ game["BDiscard"].append(index)
+ text = self.deck["blackCards"][index]["text"]
+ text = self.cleanJson(text)
+ game["BlackCard"] = {"Text": text, "Pick": self.deck["blackCards"][index]["pick"]}
+ return game["BlackCard"]
+
+ async def nextPlay(self, ctx, game):
+ # Advances the game
+ if len(game["Members"]) < self.minMembers:
+ prefix = await self.bot.db.prefix()
+ stat_embed = discord.Embed(color=discord.Color.red())
+ stat_embed.set_author(
+ name="Not enough players to continue! ({}/{})".format(
+ len(game["Members"]), self.minMembers
+ )
+ )
+ stat_embed.set_footer(
+ text="Have other users join with: {}joincah {}".format(prefix[0], game["ID"])
+ )
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ await member["User"].send(embed=stat_embed)
+ return
+
+ # Find if we have a winner
+ winner = False
+ stat_embed = discord.Embed(color=discord.Color.lighter_grey())
+ for member in game["Members"]:
+ if member["IsBot"]:
+ # Clear pending tasks and set to None
+ if not member["Task"] == None:
+ task = member["Task"]
+ if not task.done():
+ # Task isn't finished - we're on a new hand, cancel it
+ task.cancel()
+ member["Task"] = None
+ if member["Points"] >= self.winAfter:
+ # We have a winner!
+ winner = True
+ if member["IsBot"]:
+ stat_embed.set_author(
+ name="{} ({}) is the WINNER!!".format(self.botName, member["ID"])
+ )
+ else:
+ stat_embed.set_author(
+ name="{} is the WINNER!!".format(self.displayname(member["User"]))
+ )
+ stat_embed.set_footer(text="Congratulations!".format(game["ID"]))
+ break
+ if winner:
+ for member in game["Members"]:
+ if not member["IsBot"]:
+ await member["User"].send(embed=stat_embed)
+ # Reset all users
+ member["Hand"] = []
+ member["Points"] = 0
+ member["Won"] = []
+ member["Laid"] = False
+ member["Refreshed"] = False
+ return
+
+ game["Judging"] = False
+ # Clear submitted cards
+ game["Submitted"] = []
+ # We have enough members
+ if game["Judge"] == -1:
+ # First game - randomize judge
+ game["Judge"] = random.randint(0, len(game["Members"]) - 1)
+ else:
+ game["Judge"] += 1
+ # Reset the judge if out of bounds
+ if game["Judge"] >= len(game["Members"]):
+ game["Judge"] = 0
+
+ # Draw the next black card
+ bCard = await self.drawBCard(game)
+
+ # Draw cards
+ for member in game["Members"]:
+ member["Laid"] = False
+ await self.drawCards(member["ID"])
+
+ # Show hands
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ await self.showPlay(ctx, member["User"])
+ index = game["Members"].index(member)
+ if not index == game["Judge"]:
+ await self.showHand(ctx, member["User"])
+ await asyncio.sleep(0.1)
+
+ # Have the bots lay their cards
+ for member in game["Members"]:
+ if not member["IsBot"]:
+ continue
+ if member["ID"] == game["Members"][game["Judge"]]["ID"]:
+ continue
+ # Not a human player, and not the judge
+ # task = self.bot.loop.create_task(self.botPick(ctx, member, game))\
+ task = asyncio.ensure_future(self.botPick(ctx, member, game))
+ member["Task"] = task
+ # await self.botPick(ctx, member, game)
+
+ @commands.command()
+ async def game(self, ctx, *, message=None):
+ """Displays the game's current status."""
+ if not await self.checkPM(ctx.message):
+ return
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ await self.showPlay(ctx, ctx.message.author)
+
+ @commands.command()
+ async def chat(self, ctx, *, message=None):
+ """Broadcasts a message to the other players in your game."""
+ if not await self.checkPM(ctx.message):
+ return
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ userGame["Time"] = int(time.time())
+ if message == None:
+ msg = "Ooookay, you say *nothing...*"
+ await ctx.message.author.send(msg)
+ return
+ msg = "*{}* says: {}".format(ctx.message.author.name, message)
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ continue
+ # Tell them all!!
+ if not member["User"] == ctx.message.author:
+ # Don't tell yourself
+ await member["User"].send(msg)
+ else:
+ # Update member's time
+ member["Time"] = int(time.time())
+ await ctx.message.author.send("Message sent!")
+
+ @commands.command()
+ async def lay(self, ctx, *, card=None):
+ """Lays a card or cards from your hand. If multiple cards are needed, separate them by a comma (1,2,3)."""
+ if not await self.checkPM(ctx.message):
+ return
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ userGame["Time"] = int(time.time())
+ for member in userGame["Members"]:
+ if member["User"] == ctx.message.author:
+ member["Time"] = int(time.time())
+ user = member
+ index = userGame["Members"].index(member)
+ if index == userGame["Judge"]:
+ await ctx.message.author.send(
+ "You're the judge. You don't get to lay cards this round."
+ )
+ return
+ for submit in userGame["Submitted"]:
+ if submit["By"]["User"] == ctx.message.author:
+ await ctx.message.author.send("You already made your submission this round.")
+ return
+ if card == None:
+ await ctx.message.author.send("You need you input *something.*")
+ return
+ card = card.strip()
+ card = card.replace(" ", "")
+ # Not the judge
+ if len(userGame["Members"]) < self.minMembers:
+ stat_embed = discord.Embed(color=discord.Color.red())
+ stat_embed.set_author(
+ name="Not enough players to continue! ({}/{})".format(
+ len(userGame["Members"]), self.minMembers
+ )
+ )
+ stat_embed.set_footer(
+ text="Have other users join with: {}joincah {}".format(prefix[0], userGame["ID"])
+ )
+ await ctx.message.author.send(embed=stat_embed)
+ return
+
+ numberCards = userGame["BlackCard"]["Pick"]
+ cards = []
+ if numberCards > 1:
+ cardSpeak = "cards"
+ try:
+ card = card.split(",")
+ except Exception:
+ card = []
+ if not len(card) == numberCards:
+ msg = "You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`".format(
+ numberCards, prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ # Got something
+ # Check for duplicates
+ if not len(card) == len(set(card)):
+ msg = "You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`".format(
+ numberCards, prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ # Works
+ for c in card:
+ try:
+ c = int(c)
+ except Exception:
+ msg = "You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`".format(
+ numberCards, prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+
+ if c < 1 or c > len(user["Hand"]):
+ msg = "Card numbers must be between 1 and {}.".format(len(user["Hand"]))
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ cards.append(user["Hand"][c - 1]["Text"])
+ # Remove from user's hand
+ card = sorted(card, key=lambda card: int(card), reverse=True)
+ for c in card:
+ user["Hand"].pop(int(c) - 1)
+ # Valid cards
+
+ newSubmission = {"By": user, "Cards": cards}
+ else:
+ cardSpeak = "card"
+ try:
+ card = int(card)
+ except Exception:
+ msg = "You need to lay a valid card with `{}lay [card number]`".format(prefix[0])
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ if card < 1 or card > len(user["Hand"]):
+ msg = "Card numbers must be between 1 and {}.".format(len(user["Hand"]))
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+ # Valid card
+ newSubmission = {"By": user, "Cards": [user["Hand"].pop(card - 1)["Text"]]}
+ userGame["Submitted"].append(newSubmission)
+
+ # Shuffle cards
+ shuffle(userGame["Submitted"])
+
+ user["Laid"] = True
+ await ctx.message.author.send("You submitted your {}!".format(cardSpeak))
+ await self.checkSubmissions(ctx, userGame, user)
+
+ @commands.command()
+ async def pick(self, ctx, *, card=None):
+ """As the judge - pick the winning card(s)."""
+ prefix = await self.bot.db.prefix()
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ userGame["Time"] = int(time.time())
+ isJudge = False
+ for member in userGame["Members"]:
+ if member["User"] == ctx.message.author:
+ member["Time"] = int(time.time())
+ user = member
+ index = userGame["Members"].index(member)
+ if index == userGame["Judge"]:
+ isJudge = True
+ if not isJudge:
+ msg = "You're not the judge - I guess you'll have to wait your turn.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ # Am judge
+ totalUsers = len(userGame["Members"]) - 1
+ submitted = len(userGame["Submitted"])
+ if submitted < totalUsers:
+ if totalUsers - submitted == 1:
+ msg = "Still waiting on 1 card..."
+ else:
+ msg = "Still waiting on {} cards...".format(totalUsers - submitted)
+ await ctx.message.author.send(msg)
+ return
+ try:
+ card = int(card) - 1
+ except Exception:
+ card = -1
+ if card < 0 or card >= totalUsers:
+ msg = "Your pick must be between 1 and {}.".format(totalUsers)
+ await ctx.message.author.send(msg)
+ return
+ # Pick is good!
+ await self.winningCard(ctx, userGame, card)
+
+ @commands.command()
+ async def hand(self, ctx):
+ """Shows your hand."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ await self.showHand(ctx, ctx.message.author)
+ userGame["Time"] = currentTime = int(time.time())
+
+ @commands.command()
+ async def newcah(self, ctx):
+ """Starts a new Cards Against Humanity game."""
+ # if not await self.checkPM(ctx.message):
+ # return
+ # Check if the user is already in game
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ if userGame:
+ # Already in a game
+ msg = "You're already in a game (id: *{}*)\nType `{}leavecah` to leave that game.".format(
+ userGame["ID"], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+
+ # Not in a game - create a new one
+ gameID = self.randomID()
+ currentTime = int(time.time())
+ newGame = {
+ "ID": gameID,
+ "Members": [],
+ "Discard": [],
+ "BDiscard": [],
+ "Judge": -1,
+ "Time": currentTime,
+ "BlackCard": None,
+ "Submitted": [],
+ "NextHand": asyncio.Event(),
+ "Judging": False,
+ "Timeout": True,
+ }
+ member = {
+ "ID": ctx.message.author.id,
+ "User": ctx.message.author,
+ "Points": 0,
+ "Won": [],
+ "Hand": [],
+ "Laid": False,
+ "Refreshed": False,
+ "IsBot": False,
+ "Creator": True,
+ "Task": None,
+ "Time": currentTime,
+ }
+ newGame["Members"].append(member)
+ newGame["Running"] = True
+ task = self.bot.loop.create_task(self.gameCheckLoop(ctx, newGame))
+ task = self.bot.loop.create_task(self.checkCards(ctx, newGame))
+ self.games.append(newGame)
+ # Tell the user they created a new game and list its ID
+ await ctx.message.channel.send("You created game id: *{}*".format(gameID))
+ await self.drawCards(ctx.message.author)
+ # await self.showHand(ctx, ctx.message.author)
+ # await self.nextPlay(ctx, newGame)
+
+ @commands.command()
+ async def leavecah(self, ctx):
+ """Leaves the current game you're in."""
+ removeCheck = await self.removeMember(ctx.message.author)
+ if not removeCheck:
+ msg = "You are not in a game."
+ await ctx.message.channel.send(msg)
+ return
+ if self.checkGame(removeCheck):
+ # await self.nextPlay(ctx, removeCheck)
+
+ """# Start the game loop
+ event = removeCheck['NextHand']
+ self.bot.loop.call_soon_threadsafe(event.set)"""
+ # Player was removed - try to handle it calmly...
+ await self.checkSubmissions(ctx, removeCheck)
+
+ @commands.command()
+ async def joincah(self, ctx, *, id=None):
+ """Join a Cards Against Humanity game. If no id or user is passed, joins a random game."""
+ # if not await self.checkPM(ctx.message):
+ # return
+ # Check if the user is already in game
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ isCreator = False
+ if userGame:
+ # Already in a game
+ msg = "You're already in a game (id: *{}*)\nType `{}leavecah` to leave that game.".format(
+ userGame["ID"], prefix[0]
+ )
+ await ctx.message.channel.send(msg)
+ return
+ if len(self.games):
+ if id:
+ game = self.gameForID(id)
+ if game == None:
+ # That id doesn't exist - or is possibly a user
+ # If user, has to be joined from server chat
+ if not ctx.message.guild:
+ msg = "I couldn't find a game attached to that id. If you are trying to join a user - run the `{}joincah [user]` command in a channel on a server you share with that user.".format(
+ prefix[0]
+ )
+ await ctx.message.channel.send(msg)
+ return
+ else:
+ # We have a server - let's try for a user
+ member = self.memberforname(id, ctx.message.guild)
+ if not member:
+ # Couldn't find user!
+ msg = "I couldn't find a game attached to that id. If you are trying to join a user - run the `{}joincah [user]` command in a channel on a server you share with that user.".format(
+ prefix[0]
+ )
+ await ctx.message.channel.send(msg)
+ return
+ # Have a user - check if they're in a game
+ game = await self.userGame(member)
+ if not game:
+ # That user is NOT in a game!
+ msg = "That user doesn't appear to be playing."
+ await ctx.message.channel.send(msg)
+ return
+
+ else:
+ game = random.choice(self.games)
+ else:
+ # No games - create a new one
+ gameID = self.randomID()
+ currentTime = int(time.time())
+ game = {
+ "ID": gameID,
+ "Members": [],
+ "Discard": [],
+ "BDiscard": [],
+ "Judge": -1,
+ "Time": currentTime,
+ "BlackCard": None,
+ "Submitted": [],
+ "NextHand": asyncio.Event(),
+ "Judging": False,
+ "Timeout": True,
+ }
+ game["Running"] = True
+ task = self.bot.loop.create_task(self.gameCheckLoop(ctx, game))
+ task = self.bot.loop.create_task(self.checkCards(ctx, game))
+ self.games.append(game)
+ # Tell the user they created a new game and list its ID
+ await ctx.message.channel.send("**You created game id:** ***{}***".format(gameID))
+ isCreator = True
+
+ # Tell everyone else you joined
+ for member in game["Members"]:
+ if member["IsBot"]:
+ continue
+ await member["User"].send(
+ "***{}*** **joined the game!**".format(self.displayname(ctx.message.author))
+ )
+
+ # We got a user!
+ currentTime = int(time.time())
+ member = {
+ "ID": ctx.message.author.id,
+ "User": ctx.message.author,
+ "Points": 0,
+ "Won": [],
+ "Hand": [],
+ "Laid": False,
+ "Refreshed": False,
+ "IsBot": False,
+ "Creator": isCreator,
+ "Task": None,
+ "Time": currentTime,
+ }
+ game["Members"].append(member)
+ await self.drawCards(ctx.message.author)
+ if len(game["Members"]) == 1:
+ # Just created the game
+ await self.drawCards(ctx.message.author)
+ else:
+ msg = "**You've joined game id:** ***{}!***\n\nThere are *{} users* in this game.".format(
+ game["ID"], len(game["Members"])
+ )
+ await ctx.message.channel.send(msg)
+
+ # Check if adding put us at minimum members
+ if len(game["Members"]) - 1 < self.minMembers:
+ # It was - *actually* start a game
+ event = game["NextHand"]
+ self.bot.loop.call_soon_threadsafe(event.set)
+ else:
+ # It was not - just incorporate new players
+ await self.checkSubmissions(ctx, game)
+ # Reset judging flag to retrigger actions
+ game["Judging"] = False
+ # Show the user the current card and their hand
+ await self.showPlay(ctx, member["User"])
+ await self.showHand(ctx, member["User"])
+ event = game["NextHand"]
+
+ game["Time"] = int(time.time())
+
+ @commands.command()
+ async def joinbot(self, ctx):
+ """Adds a bot to the game. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can add bots."
+ await ctx.message.author.send(msg)
+ return
+ member["Time"] = int(time.time())
+ # We are the creator - let's check the number of bots
+ if botCount >= self.maxBots:
+ # Too many bots!
+ msg = "You already have enough bots (max is {}).".format(self.maxBots)
+ await ctx.message.author.send(msg)
+ return
+ # We can get another bot!
+ botID = self.randomBotID(userGame)
+ lobot = {
+ "ID": botID,
+ "User": None,
+ "Points": 0,
+ "Won": [],
+ "Hand": [],
+ "Laid": False,
+ "Refreshed": False,
+ "IsBot": True,
+ "Creator": False,
+ "Task": None,
+ }
+ userGame["Members"].append(lobot)
+ await self.drawCards(lobot["ID"])
+ msg = "***{} ({})*** **joined the game!**".format(self.botName, botID)
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ continue
+ await member["User"].send(msg)
+ # await self.nextPlay(ctx, userGame)
+
+ # Check if adding put us at minimum members
+ if len(userGame["Members"]) - 1 < self.minMembers:
+ # It was - *actually* start a game
+ event = userGame["NextHand"]
+ self.bot.loop.call_soon_threadsafe(event.set)
+ else:
+ # It was not - just incorporate new players
+ await self.checkSubmissions(ctx, userGame)
+ # Reset judging flag to retrigger actions
+ userGame["Judging"] = False
+ # Schedule stuff
+ task = asyncio.ensure_future(self.botPick(ctx, lobot, userGame))
+ lobot["Task"] = task
+
+ @commands.command()
+ async def joinbots(self, ctx, number=None):
+ """Adds bots to the game. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ prefix = await self.bot.db.prefix()
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can add bots."
+ await ctx.message.author.send(msg)
+ return
+ member["Time"] = int(time.time())
+ if number == None:
+ # No number specified - let's add the max number of bots
+ number = self.maxBots - botCount
+
+ try:
+ number = int(number)
+ except Exception:
+ msg = "Number of bots to add must be an integer."
+ await ctx.message.author.send(msg)
+ return
+
+ # We are the creator - let's check the number of bots
+ if botCount >= self.maxBots:
+ # Too many bots!
+ msg = "You already have enough bots (max is {}).".format(self.maxBots)
+ await ctx.message.author.send(msg)
+ return
+
+ if number > (self.maxBots - botCount):
+ number = self.maxBots - botCount
+
+ if number == 1:
+ msg = "**Adding {} bot:**\n\n".format(number)
+ else:
+ msg = "**Adding {} bots:**\n\n".format(number)
+
+ newBots = []
+ for i in range(0, number):
+ # We can get another bot!
+ botID = self.randomBotID(userGame)
+ lobot = {
+ "ID": botID,
+ "User": None,
+ "Points": 0,
+ "Won": [],
+ "Hand": [],
+ "Laid": False,
+ "Refreshed": False,
+ "IsBot": True,
+ "Creator": False,
+ "Task": None,
+ }
+ userGame["Members"].append(lobot)
+ newBots.append(lobot)
+ await self.drawCards(lobot["ID"])
+ msg += "***{} ({})*** **joined the game!**\n".format(self.botName, botID)
+ # await self.nextPlay(ctx, userGame)
+
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ continue
+ await member["User"].send(msg)
+
+ # Check if adding put us at minimum members
+ if len(userGame["Members"]) - number < self.minMembers:
+ # It was - *actually* start a game
+ event = userGame["NextHand"]
+ self.bot.loop.call_soon_threadsafe(event.set)
+ else:
+ # It was not - just incorporate new players
+ await self.checkSubmissions(ctx, userGame)
+ # Reset judging flag to retrigger actions
+ game["Judging"] = False
+ for bot in newBots:
+ # Schedule stuff
+ task = asyncio.ensure_future(self.botPick(ctx, bot, userGame))
+ bot["Task"] = task
+
+ @commands.command()
+ async def removebot(self, ctx, *, id=None):
+ """Removes a bot from the game. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ prefix = await self.bot.db.prefix()
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can remove bots."
+ await ctx.message.author.send(msg)
+ return
+ member["Time"] = int(time.time())
+ # We are the creator - let's check the number of bots
+ if id == None:
+ # Just remove the first bot we find
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ await self.removeMember(member["ID"])
+ """# Start the game loop
+ event = userGame['NextHand']
+ self.bot.loop.call_soon_threadsafe(event.set)"""
+ # Bot was removed - try to handle it calmly...
+ await self.checkSubmissions(ctx, userGame)
+ return
+ msg = "No bots to remove!"
+ await ctx.message.author.send(msg)
+ return
+ else:
+ # Remove a bot by id
+ if not await self.removeMember(id):
+ # not found
+ msg = "I couldn't locate that bot on this game. If you're trying to remove a player, try the `{}removeplayer [name]` command.".format(
+ prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ # await self.nextPlay(ctx, userGame)
+
+ """# Start the game loop
+ event = userGame['NextHand']
+ self.bot.loop.call_soon_threadsafe(event.set)"""
+ # Bot was removed - let's try to handle it calmly...
+ await self.checkSubmissions(ctx, userGame)
+
+ @commands.command()
+ async def cahgames(self, ctx):
+ """Displays up to 10 CAH games in progress."""
+ shuffledGames = list(self.games)
+ random.shuffle(shuffledGames)
+ if not len(shuffledGames):
+ await ctx.message.channel.send("No games being played currently.")
+ return
+
+ max = 10
+ if len(shuffledGames) < 10:
+ max = len(shuffledGames)
+ msg = "__Current CAH Games__:\n\n"
+
+ for i in range(0, max):
+ playerCount = 0
+ botCount = 0
+ gameID = shuffledGames[i]["ID"]
+ for j in shuffledGames[i]["Members"]:
+ if j["IsBot"]:
+ botCount += 1
+ else:
+ playerCount += 1
+ botText = "{} bot".format(botCount)
+ if not botCount == 1:
+ botText += "s"
+ playerText = "{} player".format(playerCount)
+ if not playerCount == 1:
+ playerText += "s"
+
+ msg += "{}. {} - {} | {}\n".format(i + 1, gameID, playerText, botText)
+
+ await ctx.message.channel.send(msg)
+
+ @commands.command()
+ async def score(self, ctx):
+ """Display the score of the current game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ stat_embed = discord.Embed(color=discord.Color.purple())
+ stat_embed.set_author(name="Current Score")
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(userGame["ID"]))
+ await ctx.message.author.send(embed=stat_embed)
+ users = sorted(userGame["Members"], key=lambda card: int(card["Points"]), reverse=True)
+ msg = ""
+ i = 0
+ if len(users) > 10:
+ msg += "__10 of {} Players:__\n\n".format(len(users))
+ else:
+ msg += "__Players:__\n\n"
+ for user in users:
+ i += 1
+ if i > 10:
+ break
+ if user["Points"] == 1:
+ if user["User"]:
+ # Person
+ msg += "{}. *{}* - 1 point\n".format(i, self.displayname(user["User"]))
+ else:
+ # Bot
+ msg += "{}. *{} ({})* - 1 point\n".format(i, self.botName, user["ID"])
+ else:
+ if user["User"]:
+ # Person
+ msg += "{}. *{}* - {} points\n".format(
+ i, self.displayname(user["User"]), user["Points"]
+ )
+ else:
+ # Bot
+ msg += "{}. *{} ({})* - {} points\n".format(
+ i, self.botName, user["ID"], user["Points"]
+ )
+ await ctx.message.author.send(msg)
+
+ @commands.command()
+ async def laid(self, ctx):
+ """Shows who laid their cards and who hasn't."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ stat_embed = discord.Embed(color=discord.Color.purple())
+ stat_embed.set_author(name="Card Check")
+ stat_embed.set_footer(text="Cards Against Humanity - id: {}".format(userGame["ID"]))
+ await ctx.message.author.send(embed=stat_embed)
+ users = sorted(userGame["Members"], key=lambda card: int(card["Laid"]))
+ msg = ""
+ i = 0
+ if len(users) > 10:
+ msg += "__10 of {} Players:__\n\n".format(len(users))
+ else:
+ msg += "__Players:__\n\n"
+ for user in users:
+ if len(userGame["Members"]) >= self.minMembers:
+ if user == userGame["Members"][userGame["Judge"]]:
+ continue
+ i += 1
+ if i > 10:
+ break
+
+ if user["Laid"]:
+ if user["User"]:
+ # Person
+ msg += "{}. *{}* - Cards are in.\n".format(i, self.displayname(user["User"]))
+ else:
+ # Bot
+ msg += "{}. *{} ({})* - Cards are in.\n".format(i, self.botName, user["ID"])
+ else:
+ if user["User"]:
+ # Person
+ msg += "{}. *{}* - Waiting for cards...\n".format(
+ i, self.displayname(user["User"])
+ )
+ else:
+ # Bot
+ msg += "{}. *{} ({})* - Waiting for cards...\n".format(
+ i, self.botName, user["ID"]
+ )
+ await ctx.message.author.send(msg)
+
+ @commands.command()
+ async def removeplayer(self, ctx, *, name=None):
+ """Removes a player from the game. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can remove players."
+ await ctx.message.author.send(msg)
+ return
+ member["Time"] = int(time.time())
+ # We are the creator - let's check the number of bots
+ if name == None:
+ # Nobody named!
+ msg = "Okay, I removed... no one from the game..."
+ await ctx.message.author.send(msg)
+ return
+
+ # Let's get the person either by name, or by id
+ nameID = "".join(list(filter(str.isdigit, name)))
+ for member in userGame["Members"]:
+ toRemove = False
+ if member["IsBot"]:
+ continue
+ if name.lower() == self.displayname(member["User"]).lower():
+ # Got em!
+ toRemove = True
+ elif nameID == member["ID"]:
+ # Got em!
+ toRemove = True
+ if toRemove:
+ await self.removeMember(member["ID"])
+ break
+ # await self.nextPlay(ctx, userGame)
+
+ if toRemove:
+ """# Start the game loop
+ event = userGame['NextHand']
+ self.bot.loop.call_soon_threadsafe(event.set)"""
+ # Player was removed - try to handle it calmly...
+ await self.checkSubmissions(ctx, userGame)
+ else:
+ prefix = await self.bot.db.prefix()
+ msg = "I couldn't locate that player on this game. If you're trying to remove a bot, try the `{}removebot [id]` command.".format(
+ prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+
+ @commands.command()
+ async def flushhand(self, ctx):
+ """Flushes the cards in your hand - can only be done once per game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ if userGame["Judge"] == -1:
+ msg = "The game hasn't started yet. Probably not worth it to flush your hand before you get it..."
+ await ctx.message.author.send(msg)
+ return
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ continue
+ if member["User"] == ctx.message.author:
+ member["Time"] = int(time.time())
+ # Found us!
+ if member["Refreshed"]:
+ # Already flushed their hand
+ msg = "You have already flushed your hand this game."
+ await ctx.message.author.send(msg)
+ return
+ else:
+ member["Hand"] = []
+ await self.drawCards(member["ID"])
+ member["Refreshed"] = True
+ msg = "Flushing your hand!"
+ await ctx.message.author.send(msg)
+ await self.showHand(ctx, ctx.message.author)
+ return
+
+ @commands.command()
+ async def idlekick(self, ctx, *, setting=None):
+ """Sets whether or not to kick members if idle for 5 minutes or more. Can only be done by the player who created the game."""
+ if not await self.checkPM(ctx.message):
+ return
+ # Check if the user is already in game
+ userGame = await self.userGame(ctx.message.author)
+ if not userGame:
+ # Not in a game
+ prefix = await self.bot.db.prefix()
+ msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(
+ prefix[0], prefix[0]
+ )
+ await ctx.message.author.send(msg)
+ return
+ botCount = 0
+ for member in userGame["Members"]:
+ if member["IsBot"]:
+ botCount += 1
+ continue
+ if member["User"] == ctx.message.author:
+ if not member["Creator"]:
+ # You didn't make this game
+ msg = "Only the player that created the game can remove bots."
+ await ctx.message.author.send(msg)
+ return
+ # We are the creator - let's check the number of bots
+ if setting == None:
+ # Output idle kick status
+ if userGame["Timeout"]:
+ await ctx.message.channel.send("Idle kick is enabled.")
+ else:
+ await ctx.message.channel.send("Idle kick is disabled.")
+ return
+ elif setting.lower() == "yes" or setting.lower() == "on" or setting.lower() == "true":
+ setting = True
+ elif setting.lower() == "no" or setting.lower() == "off" or setting.lower() == "false":
+ setting = False
+ else:
+ setting = None
+
+ if setting == True:
+ if userGame["Timeout"] == True:
+ msg = "Idle kick remains enabled."
+ else:
+ msg = "Idle kick now enabled."
+ for member in userGame["Members"]:
+ member["Time"] = int(time.time())
+ else:
+ if userGame["Timeout"] == False:
+ msg = "Idle kick remains disabled."
+ else:
+ msg = "Idle kick now disabled."
+ userGame["Timeout"] = setting
+
+ await ctx.message.channel.send(msg)
+
+ @commands.command()
+ async def cahcredits(self, ctx):
+ """Code credits."""
+ await ctx.send(
+ "```\nThis cog is made possible by CorpBot.\nPlease visit https://github.com/corpnewt/CorpBot.py for more information.\n```"
+ )