Merge pull request #87 from NikolajDanger/PEP_and_docstrings

Pep and docstrings
This commit is contained in:
Nikolaj Gade
2021-08-17 18:07:44 +02:00
committed by GitHub
153 changed files with 6147 additions and 4008 deletions

24
.gitignore vendored
View File

@ -153,16 +153,16 @@ static
token.txt
credentials.txt
options.txt
resources/starWars/destinyPoints.txt
resources/bedreNetflix/
resources/games/hilo/
resources/games/blackjackTables/
resources/games/oldImages/
resources/games/connect4Boards/
resources/games/hexBoards/
resources/games/hangmanBoards/
resources/lookup/monsters.json
resources/lookup/spells.json
resources/movies.txt
resources/names.txt
gwendolyn/resources/star_wars/destinyPoints.txt
gwendolyn/resources/plex/
gwendolyn/resources/games/hilo/
gwendolyn/resources/games/blackjack_tables/
gwendolyn/resources/games/old_images/
gwendolyn/resources/games/connect_four_boards/
gwendolyn/resources/games/hex_boards/
gwendolyn/resources/games/hangman_boards/
gwendolyn/resources/lookup/monsters.json
gwendolyn/resources/lookup/spells.json
gwendolyn/resources/movies.txt
gwendolyn/resources/names.txt
gwendolynTest.py

View File

@ -1,81 +0,0 @@
import os, finnhub, platform, asyncio, discord
from discord.ext import commands
from discord_slash import SlashCommand
from pymongo import MongoClient
from funcs import Money, StarWars, Games, Other, LookupFuncs
from utils import Options, Credentials, logThis, makeFiles, databaseFuncs, EventHandler, ErrorHandler
class Gwendolyn(commands.Bot):
def __init__(self):
self.options = Options()
self.credentials = Credentials()
self.finnhubClient = finnhub.Client(api_key = self.credentials.finnhubKey)
self.MongoClient = MongoClient(f"mongodb+srv://{self.credentials.mongoDBUser}:{self.credentials.mongoDBPassword}@gwendolyn.qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority")
if self.options.testing:
self.log("Testing mode")
self.database = self.MongoClient["Gwendolyn-Test"]
else:
self.database = self.MongoClient["Gwendolyn"]
self.starWars = StarWars(self)
self.other = Other(self)
self.lookupFuncs = LookupFuncs(self)
self.games = Games(self)
self.money = Money(self)
self.databaseFuncs = databaseFuncs(self)
self.eventHandler = EventHandler(self)
self.errorHandler = ErrorHandler(self)
intents = discord.Intents.default()
intents.members = True
super().__init__(command_prefix=" ", case_insensitive=True, intents = intents, status = discord.Status.dnd)
def log(self, messages, channel : str = "", level : int = 20):
logThis(messages, channel, level)
async def stop(self, ctx):
if f"#{ctx.author.id}" in self.options.admins:
await ctx.send("Pulling git repo and restarting...")
await self.change_presence(status = discord.Status.offline)
self.databaseFuncs.stopServer()
self.log("Logging out", level = 25)
await self.close()
else:
self.log(f"{ctx.author.display_name} tried to stop me! (error code 201)",str(ctx.channel_id))
await ctx.send(f"I don't think I will, {ctx.author.display_name} (error code 201)")
async def defer(self, ctx):
try:
await ctx.defer()
except:
self.log("defer failed")
if __name__ == "__main__":
if platform.system() == "Windows":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# Creates the required files
makeFiles()
# Creates the Bot
bot = Gwendolyn()
bot.slash = SlashCommand(bot, sync_commands=True, sync_on_cog_reload=True, override_type=True)
#Loads cogs
for filename in os.listdir("./cogs"):
if filename.endswith(".py"):
bot.load_extension(f"cogs.{filename[:-3]}")
try:
# Runs the whole shabang
bot.run(bot.credentials.token)
except:
bot.log("Could not log in. Remember to write your bot token in the credentials.txt file")

View File

@ -1,34 +0,0 @@
from discord.ext import commands
class EventCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.bot.on_error = self.on_error
# Syncs commands, sets the game, and logs when the bot logs in
@commands.Cog.listener()
async def on_ready(self):
await self.bot.eventHandler.on_ready()
# Logs when user sends a command
@commands.Cog.listener()
async def on_slash_command(self, ctx):
await self.bot.eventHandler.on_slash_command(ctx)
# Logs if a command experiences an error
@commands.Cog.listener()
async def on_slash_command_error(self, ctx, error):
await self.bot.errorHandler.on_slash_command_error(ctx, error)
# Logs if on error occurs
async def on_error(self, method, *args, **kwargs):
await self.bot.errorHandler.on_error(method)
# If someone reacted to a message, checks if it's a reaction it's
# Gwendolyn has been waiting for, and then does something
@commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
await self.bot.eventHandler.on_reaction_add(reaction, user)
def setup(bot):
bot.add_cog(EventCog(bot))

View File

@ -1,155 +0,0 @@
from discord.ext import commands
from discord_slash import cog_ext
from utils import getParams
params = getParams()
class GamesCog(commands.Cog):
def __init__(self,bot):
"""Runs game stuff."""
self.bot = bot
# Checks user balance
@cog_ext.cog_slash(**params["balance"])
async def balance(self, ctx):
await self.bot.money.sendBalance(ctx)
# Gives another user an amount of GwendoBucks
@cog_ext.cog_slash(**params["give"])
async def give(self, ctx, user, amount):
await self.bot.money.giveMoney(ctx, user, amount)
# Invest GwendoBucks in the stock market
@cog_ext.cog_slash(**params["invest"])
async def invest(self, ctx, parameters = "check"):
await self.bot.games.invest.parseInvest(ctx, parameters)
# Runs a game of trivia
@cog_ext.cog_slash(**params["trivia"])
async def trivia(self, ctx, answer = ""):
await self.bot.games.trivia.triviaParse(ctx, answer)
class BlackjackCog(commands.Cog):
def __init__(self,bot):
"""Runs game stuff."""
self.bot = bot
# Starts a game of blackjack
@cog_ext.cog_subcommand(**params["blackjackStart"])
async def blackjackStart(self, ctx):
await self.bot.games.blackjack.start(ctx)
@cog_ext.cog_subcommand(**params["blackjackBet"])
async def blackjackBet(self, ctx, bet):
await self.bot.games.blackjack.playerDrawHand(ctx, bet)
@cog_ext.cog_subcommand(**params["blackjackStand"])
async def blackjackStand(self, ctx, hand = ""):
await self.bot.games.blackjack.stand(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjackHit"])
async def blackjackHit(self, ctx, hand = 0):
await self.bot.games.blackjack.hit(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjackDouble"])
async def blackjackDouble(self, ctx, hand = 0):
await self.bot.games.blackjack.double(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjackSplit"])
async def blackjackSplit(self, ctx, hand = 0):
await self.bot.games.blackjack.split(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjackHilo"])
async def blackjackHilo(self, ctx):
await self.bot.games.blackjack.hilo(ctx)
@cog_ext.cog_subcommand(**params["blackjackShuffle"])
async def blackjackShuffle(self, ctx):
await self.bot.games.blackjack.shuffle(ctx)
@cog_ext.cog_subcommand(**params["blackjackCards"])
async def blackjackCards(self, ctx):
await self.bot.games.blackjack.cards(ctx)
class ConnectFourCog(commands.Cog):
def __init__(self,bot):
"""Runs game stuff."""
self.bot = bot
# Start a game of connect four against a user
@cog_ext.cog_subcommand(**params["connectFourStartUser"])
async def connectFourStartUser(self, ctx, user):
await self.bot.games.connectFour.start(ctx, user)
# Start a game of connect four against gwendolyn
@cog_ext.cog_subcommand(**params["connectFourStartGwendolyn"])
async def connectFourStartGwendolyn(self, ctx, difficulty = 3):
await self.bot.games.connectFour.start(ctx, difficulty)
# Stop the current game of connect four
@cog_ext.cog_subcommand(**params["connectFourSurrender"])
async def connectFourSurrender(self, ctx):
await self.bot.games.connectFour.surrender(ctx)
class HangmanCog(commands.Cog):
def __init__(self,bot):
"""Runs game stuff."""
self.bot = bot
# Starts a game of Hangman
@cog_ext.cog_subcommand(**params["hangmanStart"])
async def hangmanStart(self, ctx):
await self.bot.games.hangman.start(ctx)
# Stops a game of Hangman
@cog_ext.cog_subcommand(**params["hangmanStop"])
async def hangmanStop(self, ctx):
await self.bot.games.hangman.stop(ctx)
class HexCog(commands.Cog):
def __init__(self,bot):
"""Runs game stuff."""
self.bot = bot
# Start a game of Hex against another user
@cog_ext.cog_subcommand(**params["hexStartUser"])
async def hexStartUser(self, ctx, user):
await self.bot.games.hex.start(ctx, user)
# Start a game of Hex against Gwendolyn
@cog_ext.cog_subcommand(**params["hexStartGwendolyn"])
async def hexStartGwendolyn(self, ctx, difficulty = 2):
await self.bot.games.hex.start(ctx, difficulty)
# Place a piece in the hex game
@cog_ext.cog_subcommand(**params["hexPlace"])
async def hexPlace(self, ctx, coordinates):
await self.bot.games.hex.placeHex(ctx, coordinates, f"#{ctx.author.id}")
# Undo your last hex move
@cog_ext.cog_subcommand(**params["hexUndo"])
async def hexUndo(self, ctx):
await self.bot.games.hex.undo(ctx)
# Perform a hex swap
@cog_ext.cog_subcommand(**params["hexSwap"])
async def hexSwap(self, ctx):
await self.bot.games.hex.swap(ctx)
# Surrender the hex game
@cog_ext.cog_subcommand(**params["hexSurrender"])
async def hexSurrender(self, ctx):
await self.bot.games.hex.surrender(ctx)
def setup(bot):
bot.add_cog(GamesCog(bot))
bot.add_cog(BlackjackCog(bot))
bot.add_cog(ConnectFourCog(bot))
bot.add_cog(HangmanCog(bot))
bot.add_cog(HexCog(bot))

View File

@ -1,24 +0,0 @@
from discord.ext import commands
from discord_slash import cog_ext
from utils import getParams
params = getParams()
class LookupCog(commands.Cog):
def __init__(self, bot):
"""Runs lookup commands."""
self.bot = bot
# Looks up a spell
@cog_ext.cog_slash(**params["spell"])
async def spell(self, ctx, query):
await self.bot.lookupFuncs.spellFunc(ctx, query)
# Looks up a monster
@cog_ext.cog_slash(**params["monster"])
async def monster(self, ctx, query):
await self.bot.lookupFuncs.monsterFunc(ctx, query)
def setup(bot):
bot.add_cog(LookupCog(bot))

View File

@ -1,94 +0,0 @@
import discord, codecs, string, json
from discord.ext import commands
from discord_slash import cog_ext
from utils import getParams # pylint: disable=import-error
params = getParams()
class MiscCog(commands.Cog):
def __init__(self, bot):
"""Runs misc commands."""
self.bot = bot
self.bot.remove_command("help")
self.generators = bot.other.generators
self.bedreNetflix = bot.other.bedreNetflix
self.nerdShit = bot.other.nerdShit
# Sends the bot's latency
@cog_ext.cog_slash(**params["ping"])
async def ping(self, ctx):
await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms")
# Restarts the bot
@cog_ext.cog_slash(**params["stop"])
async def stop(self, ctx):
await self.bot.stop(ctx)
# Gets help for specific command
@cog_ext.cog_slash(**params["help"])
async def helpCommand(self, ctx, command = ""):
await self.bot.other.helpFunc(ctx, command)
# Lets you thank the bot
@cog_ext.cog_slash(**params["thank"])
async def thank(self, ctx):
await ctx.send("You're welcome :blush:")
# Sends a friendly message
@cog_ext.cog_slash(**params["hello"])
async def hello(self, ctx):
await self.bot.other.helloFunc(ctx)
# Rolls dice
@cog_ext.cog_slash(**params["roll"])
async def roll(self, ctx, dice = "1d20"):
await self.bot.other.rollDice(ctx, dice)
# Sends a random image
@cog_ext.cog_slash(**params["image"])
async def image(self, ctx):
await self.bot.other.imageFunc(ctx)
# Finds a random movie
@cog_ext.cog_slash(**params["movie"])
async def movie(self, ctx):
await self.bot.other.movieFunc(ctx)
# Generates a random name
@cog_ext.cog_slash(**params["name"])
async def name(self, ctx):
await self.generators.nameGen(ctx)
# Generates a random tavern name
@cog_ext.cog_slash(**params["tavern"])
async def tavern(self, ctx):
await self.generators.tavernGen(ctx)
# Finds a page on the Senkulpa wiki
@cog_ext.cog_slash(**params["wiki"])
async def wiki(self, ctx, wikiPage = ""):
await self.bot.other.findWikiPage(ctx, wikiPage)
#Searches for movie and adds it to Bedre Netflix
@cog_ext.cog_slash(**params["addMovie"])
async def addMovie(self, ctx, movie):
await self.bedreNetflix.requestMovie(ctx, movie)
#Searches for show and adds it to Bedre Netflix
@cog_ext.cog_slash(**params["addShow"])
async def addShow(self, ctx, show):
await self.bedreNetflix.requestShow(ctx, show)
#Returns currently downloading torrents
@cog_ext.cog_slash(**params["downloading"])
async def downloading(self, ctx, parameters = "-d"):
await self.bedreNetflix.downloading(ctx, parameters)
#Looks up on Wolfram Alpha
@cog_ext.cog_slash(**params["wolf"])
async def wolf(self, ctx, query):
await self.nerdShit.wolfSearch(ctx, query)
def setup(bot):
bot.add_cog(MiscCog(bot))

View File

@ -1,37 +0,0 @@
import discord, string, json
from discord.ext import commands
from discord_slash import cog_ext
from utils import getParams
params = getParams()
class starWarsCog(commands.Cog):
def __init__(self, bot):
"""Runs star wars commands."""
self.bot = bot
# Rolls star wars dice
@cog_ext.cog_slash(**params["starWarsRoll"])
async def starWarsRoll(self, ctx, dice = ""):
await self.bot.starWars.roll.parseRoll(ctx, dice)
# Controls destiny points
@cog_ext.cog_slash(**params["starWarsDestiny"])
async def starWarsDestiny(self, ctx, parameters = ""):
await self.bot.starWars.destiny.parseDestiny(ctx, parameters)
# Rolls for critical injuries
@cog_ext.cog_slash(**params["starWarsCrit"])
async def starWarsCrit(self, ctx, severity : int = 0):
await self.bot.starWars.roll.critRoll(ctx, severity)
# Accesses and changes character sheet data with the parseChar function
# from funcs/starWarsFuncs/starWarsCharacter.py
@cog_ext.cog_slash(**params["starWarsCharacter"])
async def starWarsCharacter(self, ctx, parameters = ""):
await self.bot.starWars.character.parseChar(ctx, parameters)
def setup(bot):
bot.add_cog(starWarsCog(bot))

View File

@ -1,966 +0,0 @@
import random
import math
import datetime
import asyncio
import discord
from PIL import Image, ImageDraw, ImageFont
from shutil import copyfile
from utils import replaceMultiple
class Blackjack():
def __init__(self,bot):
self.bot = bot
self.draw = DrawBlackjack(bot)
# Shuffles the blackjack cards
def blackjackShuffle(self, decks, channel):
self.bot.log("Shuffling the blackjack deck")
with open("resources/games/deckOfCards.txt","r") as f:
deck = f.read()
allDecks = deck.split("\n") * decks
random.shuffle(allDecks)
self.bot.database["blackjack cards"].update_one({"_id":channel},{"$set":{"_id":channel,"cards":allDecks}},upsert=True)
# Creates hilo file
self.bot.log("creating hilo doc for "+channel)
data = 0
self.bot.database["hilo"].update_one({"_id":channel},{"$set":{"_id":channel,"hilo":data}},upsert=True)
return
# Calculates the value of a blackjack hand
def calcHandValue(self, hand : list):
self.bot.log("Calculating hand value")
values = []
values.append(0)
for card in hand:
cardValue = card[0]
cardValue = replaceMultiple(cardValue,["0","k","q","j"],"10")
if cardValue == "a":
length = len(values)
for x in range(length):
values.append(values[x] + 11)
values[x] += 1
else:
for x in range(len(values)):
values[x] += int(cardValue)
values.sort()
handValue = values[0]
for value in values:
if value <= 21:
handValue = value
self.bot.log("Calculated "+str(hand)+" to be "+str(handValue))
return handValue
# Draws a card from the deck
def drawCard(self, channel):
self.bot.log("drawing a card")
drawnCard = self.bot.database["blackjack cards"].find_one({"_id":channel})["cards"][0]
self.bot.database["blackjack cards"].update_one({"_id":channel},{"$pop":{"cards":-1}})
value = self.calcHandValue([drawnCard])
if value <= 6:
self.bot.database["hilo"].update_one({"_id":channel},{"$inc":{"hilo":1}})
elif value >= 10:
self.bot.database["hilo"].update_one({"_id":channel},{"$inc":{"hilo":-1}})
return drawnCard
# Dealer draws a card and checks if they should draw another one
def dealerDraw(self,channel):
game = self.bot.database["blackjack games"].find_one({"_id":channel})
done = False
dealerHand = game["dealer hand"]
if self.calcHandValue(dealerHand) < 17:
dealerHand.append(self.drawCard(channel))
self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"dealer hand":dealerHand}})
else:
done = True
if self.calcHandValue(dealerHand) > 21:
self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"dealer busted":True}})
return done
# Goes to the next round and calculates some stuff
def blackjackContinue(self, channel):
self.bot.log("Continuing blackjack game")
game = self.bot.database["blackjack games"].find_one({"_id":channel})
done = False
self.bot.database["blackjack games"].update_one({"_id":channel},{"$inc":{"round":1}})
allStanding = True
preAllStanding = True
message = "All players are standing. The dealer now shows his cards and draws."
if game["all standing"]:
self.bot.log("All are standing")
done = self.dealerDraw(channel)
message = "The dealer draws a card."
game = self.bot.database["blackjack games"].find_one({"_id":channel})
self.bot.log("Testing if all are standing")
for user in game["user hands"]:
try:
newUser, allStanding, preAllStanding = self.testIfStanding(game["user hands"][user],allStanding,preAllStanding,True)
self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"user hands."+user:newUser}})
except:
self.bot.log("Error in testing if all are standing (error code 1331)")
if allStanding:
self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"all standing":True}})
try:
self.draw.drawImage(channel)
except:
self.bot.log("Error drawing blackjack table (error code 1340)")
if allStanding:
if done == False:
return message, True, done
else:
return "The dealer is done drawing cards", True, done
elif preAllStanding:
return "", True, done
else:
if game["round"] == 1:
firstRoundMessage = ". You can also double down with \"/blackjack double\" or split with \"/blackjack split\""
else:
firstRoundMessage = ""
return "You have 2 minutes to either hit or stand with \"/blackjack hit\" or \"/blackjack stand\""+firstRoundMessage+". It's assumed you're standing if you don't make a choice.", False, done
def testIfStanding(self, hand,allStanding,preAllStanding,topLevel):
if hand["hit"] == False:
hand["standing"] = True
if hand["standing"] == False:
allStanding = False
if self.calcHandValue(hand["hand"]) >= 21 or hand["doubled"]:
hand["standing"] = True
else:
preAllStanding = False
hand["hit"] = False
if topLevel:
if hand["split"] >= 1:
hand["other hand"], allStanding, preAllStanding = self.testIfStanding(hand["other hand"],allStanding,preAllStanding,False)
if hand["split"] >= 2:
hand["third hand"], allStanding, preAllStanding = self.testIfStanding(hand["third hand"],allStanding,preAllStanding,False)
if hand["split"] >= 3:
hand["fourth hand"], allStanding, preAllStanding = self.testIfStanding(hand["fourth hand"],allStanding,preAllStanding,False)
return hand, allStanding, preAllStanding
# When players try to hit
async def hit(self, ctx, handNumber = 0):
await self.bot.defer(ctx)
channel = str(ctx.channel_id)
user = f"#{ctx.author.id}"
roundDone = False
game = self.bot.database["blackjack games"].find_one({"_id":channel})
if user in game["user hands"]:
userHands = game["user hands"][user]
hand, handNumber = self.getHandNumber(userHands, handNumber)
if hand == None:
logMessage = "They didn't specify a hand"
sendMessage = "You need to specify a hand"
elif game["round"] <= 0:
logMessage = "They tried to hit on the 0th round"
sendMessage = "You can't hit before you see your cards"
elif hand["hit"]:
logMessage = "They've already hit this round"
sendMessage = "You've already hit this round"
elif hand["standing"]:
logMessage = "They're already standing"
sendMessage = "You can't hit when you're standing"
else:
hand["hand"].append(self.drawCard(channel))
hand["hit"] = True
handValue = self.calcHandValue(hand["hand"])
if handValue > 21:
hand["busted"] = True
if handNumber == 2:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".other hand":hand}})
elif handNumber == 3:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".third hand":hand}})
elif handNumber == 4:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".fourth hand":hand}})
else:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user:hand}})
roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel}))
sendMessage = f"{ctx.author.display_name} hit"
logMessage = "They succeeded"
else:
logMessage = "They tried to hit without being in the game"
sendMessage = "You have to enter the game before you can hit"
await ctx.send(sendMessage)
self.bot.log(logMessage)
if roundDone:
gameID = game["gameID"]
self.bot.log("Hit calling self.blackjackLoop()", channel)
await self.blackjackLoop(ctx.channel, game["round"]+1, gameID)
# When players try to double down
async def double(self, ctx, handNumber = 0):
await self.bot.defer(ctx)
channel = str(ctx.channel_id)
user = f"#{ctx.author.id}"
roundDone = False
game = self.bot.database["blackjack games"].find_one({"_id":channel})
if user in game["user hands"]:
hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber)
if hand == None:
logMessage = "They didn't specify a hand"
sendMessage = "You need to specify a hand"
elif game["round"] <= 0:
logMessage = "They tried to hit on the 0th round"
sendMessage = "You can't hit before you see your cards"
elif hand["hit"]:
logMessage = "They've already hit this round"
sendMessage = "You've already hit this round"
elif hand["standing"]:
logMessage = "They're already standing"
sendMessage = "You can't hit when you're standing"
elif len(hand["hand"]) != 2:
logMessage = "They tried to double after round 1"
sendMessage = "You can only double on the first round"
else:
bet = hand["bet"]
if self.bot.money.checkBalance(user) < bet:
logMessage = "They tried to double without being in the game"
sendMessage = "You can't double when you're not in the game"
else:
self.bot.money.addMoney(user,-1 * bet)
hand["hand"].append(self.drawCard(channel))
hand["hit"] = True
hand["doubled"] = True
hand["bet"] += bet
handValue = self.calcHandValue(hand["hand"])
if handValue > 21:
hand["busted"] = True
if handNumber == 2:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".other hand":hand}})
elif handNumber == 3:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".third hand":hand}})
elif handNumber == 4:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".fourth hand":hand}})
else:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user:hand}})
roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel}))
sendMessage = f"Adding another {bet} GwendoBucks to {self.bot.databaseFuncs.getName(user)}'s bet and drawing another card."
logMessage = "They succeeded"
else:
logMessage = "They tried to double without being in the game"
sendMessage = "You can't double when you're not in the game"
await ctx.send(sendMessage)
self.bot.log(logMessage)
if roundDone:
gameID = game["gameID"]
self.bot.log("Double calling self.blackjackLoop()", channel)
await self.blackjackLoop(ctx.channel, game["round"]+1, gameID)
# When players try to stand
async def stand(self, ctx, handNumber = 0):
await self.bot.defer(ctx)
channel = str(ctx.channel_id)
user = f"#{ctx.author.id}"
roundDone = False
game = self.bot.database["blackjack games"].find_one({"_id":channel})
if user in game["user hands"]:
hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber)
if hand == None:
sendMessage = "You need to specify which hand"
logMessage = "They didn't specify a hand"
elif game["round"] <= 0:
sendMessage = "You can't stand before you see your cards"
logMessage = "They tried to stand on round 0"
elif hand["hit"]:
sendMessage = "You've already hit this round"
logMessage = "They'd already hit this round"
elif hand["standing"]:
sendMessage = "You're already standing"
logMessage = "They're already standing"
else:
hand["standing"] = True
if handNumber == 2:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".other hand":hand}})
elif handNumber == 3:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".third hand":hand}})
elif handNumber == 4:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".fourth hand":hand}})
else:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user:hand}})
roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel}))
sendMessage = f"{ctx.author.display_name} is standing"
logMessage = "They succeeded"
else:
logMessage = "They tried to stand without being in the game"
sendMessage = "You have to enter the game before you can stand"
await ctx.send(sendMessage)
self.bot.log(logMessage)
if roundDone:
gameID = game["gameID"]
self.bot.log("Stand calling self.blackjackLoop()", channel)
await self.blackjackLoop(ctx.channel, game["round"]+1, gameID)
# When players try to split
async def split(self, ctx, handNumber = 0):
await self.bot.defer(ctx)
channel = str(ctx.channel_id)
user = f"#{ctx.author.id}"
roundDone = False
handNumberError = False
game = self.bot.database["blackjack games"].find_one({"_id":channel})
if game["user hands"][user]["split"] == 0:
hand = game["user hands"][user]
newHand = game["user hands"][user]["other hand"]
handNumber = 0
otherHand = 2
else:
if handNumber == 1:
hand = game["user hands"][user]
elif handNumber == 2:
hand = game["user hands"][user]["other hand"]
elif handNumber == 3:
hand = game["user hands"][user]["third hand"]
else:
handNumberError = True
if game["user hands"][user]["split"] == 1:
newHand = game["user hands"][user]["third hand"]
otherHand = 3
else:
newHand = game["user hands"][user]["fourth hand"]
otherHand = 4
if handNumberError:
logMessage = "They didn't specify a hand"
sendMessage = "You have to specify the hand you're hitting with"
elif game["round"] == 0:
logMessage = "They tried to split on round 0"
sendMessage = "You can't split before you see your cards"
elif game["user hands"][user]["split"] > 3:
logMessage = "They tried to split more than three times"
sendMessage = "You can only split 3 times"
elif hand["hit"]:
logMessage = "They've already hit"
sendMessage = "You've already hit"
elif hand["standing"]:
logMessage = "They're already standing"
sendMessage = "You're already standing"
elif len(hand["hand"]) != 2:
logMessage = "They tried to split after the first round"
sendMessage = "You can only split on the first round"
else:
firstCard = self.calcHandValue([hand["hand"][0]])
secondCard = self.calcHandValue([hand["hand"][1]])
if firstCard != secondCard:
logMessage = "They tried to split two different cards"
sendMessage = "You can only split if your cards have the same value"
else:
bet = hand["bet"]
if self.bot.money.checkBalance(user) < bet:
logMessage = "They didn't have enough GwendoBucks"
sendMessage = "You don't have enough GwendoBucks"
else:
self.bot.money.addMoney(user,-1 * bet)
hand["hit"] = True
newHand["hit"] = True
newHand = {
"hand":[],"bet":0,"standing":False,"busted":False,
"blackjack":False,"hit":True,"doubled":False}
newHand["bet"] = hand["bet"]
newHand["hand"].append(hand["hand"].pop(1))
newHand["hand"].append(self.drawCard(channel))
hand["hand"].append(self.drawCard(channel))
handValue = self.calcHandValue(hand["hand"])
otherHandValue = self.calcHandValue(newHand["hand"])
if handValue > 21:
hand["busted"] = True
elif handValue == 21:
hand["blackjack"] = True
if otherHandValue > 21:
newHand["busted"] = True
elif otherHandValue == 21:
newHand["blackjack"] = True
if handNumber == 2:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".other hand":hand}})
elif handNumber == 3:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".third hand":hand}})
else:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user:hand}})
if otherHand == 3:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".third hand":newHand}})
elif otherHand == 4:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".fourth hand":newHand}})
else:
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$set":{"user hands."+user+".other hand":newHand}})
self.bot.database["blackjack games"].update_one({"_id":channel},
{"$inc":{"user hands."+user+".split":1}})
roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel}))
sendMessage = f"Splitting {self.bot.databaseFuncs.getName(user)}'s hand into 2. Adding their original bet to the second hand. You can use \"/blackjack hit/stand/double 1\" and \"/blackjack hit/stand/double 2\" to play the different hands."
logMessage = "They succeeded"
await ctx.send(sendMessage)
self.bot.log(logMessage)
if roundDone:
gameID = game["gameID"]
self.bot.log("Stand calling self.blackjackLoop()", channel)
await self.blackjackLoop(ctx.channel, game["round"]+1, gameID)
# Player enters the game and draws a hand
async def playerDrawHand(self, ctx, bet : int):
await self.bot.defer(ctx)
channel = str(ctx.channel_id)
user = f"#{ctx.author.id}"
collection = self.bot.database["blackjack games"]
game = collection.find_one({"_id":channel})
userName = self.bot.databaseFuncs.getName(user)
self.bot.log(f"{userName} is trying to join the Blackjack game")
if game == None:
sendMessage = "There is no game going on in this channel"
logMessage = sendMessage
elif user in game["user hands"]:
sendMessage = "You're already in the game!"
logMessage = "They're already in the game"
elif len(game["user hands"]) >= 5:
sendMessage = "There can't be more than 5 players in a game"
logMessage = "There were already 5 players in the game"
elif game["round"] != 0:
sendMessage = "The table is no longer taking bets"
logMessage = "They tried to join after the game begun"
elif bet < 0:
sendMessage = "You can't bet a negative amount"
logMessage = "They tried to bet a negative amount"
elif self.bot.money.checkBalance(user) < bet:
sendMessage = "You don't have enough GwendoBucks"
logMessage = "They didn't have enough GwendoBucks"
else:
self.bot.money.addMoney(user,-1 * bet)
playerHand = [self.drawCard(channel) for _ in range(2)]
handValue = self.calcHandValue(playerHand)
if handValue == 21:
blackjackHand = True
else:
blackjackHand = False
newHand = {"hand":playerHand, "bet":bet, "standing":False,
"busted":False, "blackjack":blackjackHand, "hit":True,
"doubled":False, "split":0, "other hand":{},
"third hand":{}, "fourth hand":{}}
function = {"$set":{f"user hands.{user}":newHand}}
collection.update_one({"_id":channel}, function)
enterGameText = "entered the game with a bet of"
betText = f"{bet} GwendoBucks"
sendMessage = f"{userName} {enterGameText} {betText}"
logMessage = sendMessage
self.bot.log(sendMessage)
await ctx.send(logMessage)
# Starts a game of blackjack
async def start(self, ctx):
await self.bot.defer(ctx)
channel = str(ctx.channel_id)
blackjackMinCards = 50
blackjackDecks = 4
await ctx.send("Starting a new game of blackjack")
cardsLeft = 0
cards = self.bot.database["blackjack cards"].find_one({"_id":channel})
if cards != None:
cardsLeft = len(cards["cards"])
# Shuffles if not enough cards
if cardsLeft < blackjackMinCards:
self.blackjackShuffle(blackjackDecks, channel)
self.bot.log("Shuffling the blackjack deck...", channel)
await ctx.channel.send("Shuffling the deck...")
game = self.bot.database["blackjack games"].find_one({"_id":channel})
self.bot.log("Trying to start a blackjack game in "+channel)
gameStarted = False
if game == None:
dealerHand = [self.drawCard(channel),self.drawCard(channel)]
gameID = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
newGame = {"_id":channel,"dealer hand": dealerHand,"dealer busted":False,"dealer blackjack":False,"user hands": {},"all standing":False,"round":0,"gameID":gameID}
if self.calcHandValue(dealerHand) == 21:
newGame["dealer blackjack"] = True
self.bot.database["blackjack games"].insert_one(newGame)
copyfile("resources/games/blackjackTable.png","resources/games/blackjackTables/blackjackTable"+channel+".png")
gameStarted = True
if gameStarted:
sendMessage = "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds."
await ctx.channel.send(sendMessage)
filePath = f"resources/games/blackjackTables/blackjackTable{channel}.png"
oldImage = await ctx.channel.send(file = discord.File(filePath))
with open("resources/games/oldImages/blackjack"+channel, "w") as f:
f.write(str(oldImage.id))
await asyncio.sleep(30)
gamedone = False
game = self.bot.database["blackjack games"].find_one({"_id":str(channel)})
if len(game["user hands"]) == 0:
gamedone = True
await ctx.channel.send("No one entered the game. Ending the game.")
gameID = game["gameID"]
# Loop of game rounds
if gamedone == False:
self.bot.log("start() calling blackjackLoop()", channel)
await self.blackjackLoop(ctx.channel,1,gameID)
else:
new_message = self.blackjackFinish(channel)
await ctx.channel.send(new_message)
else:
await ctx.channel.send("There's already a blackjack game going on. Try again in a few minutes.")
self.bot.log("There was already a game going on")
# Ends the game and calculates winnings
def blackjackFinish(self,channel):
finalWinnings = "*Final Winnings:*\n"
game = self.bot.database["blackjack games"].find_one({"_id":channel})
dealerValue = self.calcHandValue(game["dealer hand"])
dealerBlackjack = game["dealer blackjack"]
dealerBusted = game["dealer busted"]
try:
for user in game["user hands"]:
winnings, netWinnings, reason = self.calcWinnings(game["user hands"][user],dealerValue,True,dealerBlackjack,dealerBusted)
if winnings < 0:
if winnings == -1:
finalWinnings += self.bot.databaseFuncs.getName(user)+" lost "+str(-1 * winnings)+" GwendoBuck "+reason+"\n"
else:
finalWinnings += self.bot.databaseFuncs.getName(user)+" lost "+str(-1 * winnings)+" GwendoBucks "+reason+"\n"
else:
if winnings == 1:
finalWinnings += self.bot.databaseFuncs.getName(user)+" won "+str(winnings)+" GwendoBuck "+reason+"\n"
else:
finalWinnings += self.bot.databaseFuncs.getName(user)+" won "+str(winnings)+" GwendoBucks "+reason+"\n"
self.bot.money.addMoney(user,netWinnings)
except:
self.bot.log("Error calculating winnings (error code 1311)")
self.bot.database["blackjack games"].delete_one({"_id":channel})
return finalWinnings
def calcWinnings(self,hand, dealerValue, topLevel, dealerBlackjack, dealerBusted):
self.bot.log("Calculating winnings")
reason = ""
bet = hand["bet"]
winnings = -1 * bet
netWinnings = 0
handValue = self.calcHandValue(hand["hand"])
if hand["blackjack"] and dealerBlackjack == False:
reason += "(blackjack)"
winnings += math.floor(2.5 * bet)
netWinnings += math.floor(2.5 * bet)
elif dealerBlackjack:
reason += "(dealer blackjack)"
elif hand["busted"]:
reason += "(busted)"
else:
if dealerBusted:
reason = "(dealer busted)"
winnings += 2 * bet
netWinnings += 2 * bet
elif handValue > dealerValue:
winnings += 2 * bet
netWinnings += 2 * bet
reason = "(highest value)"
elif handValue == dealerValue:
reason = "(pushed)"
winnings += bet
netWinnings += bet
else:
reason = "(highest value)"
if topLevel:
if hand["split"] >= 1:
winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["other hand"],dealerValue,False,dealerBlackjack,dealerBusted)
winnings += winningsTemp
netWinnings += netWinningsTemp
reason += reasonTemp
if hand["split"] >= 2:
winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["third hand"],dealerValue,False,dealerBlackjack,dealerBusted)
winnings += winningsTemp
netWinnings += netWinningsTemp
reason += reasonTemp
if hand["split"] >= 3:
winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["fourth hand"],dealerValue,False,dealerBlackjack,dealerBusted)
winnings += winningsTemp
netWinnings += netWinningsTemp
reason += reasonTemp
return winnings, netWinnings, reason
def getHandNumber(self, user,handNumber):
try:
hand = None
if user["split"] == 0:
hand = user
handNumber = 0
else:
if handNumber != 0:
if handNumber == 1:
hand = user
elif handNumber == 2:
hand = user["other hand"]
elif handNumber == 3:
hand = user["third hand"]
elif handNumber == 4:
hand = user["fourth hand"]
return hand, handNumber
except:
self.bot.log("Problem with getHandNumber() (error code 1322)")
def isRoundDone(self,game):
roundDone = True
for person in game["user hands"].values():
if person["hit"] == False and person["standing"] == False:
roundDone = False
if person["split"] > 0:
if person["other hand"]["hit"] == False and person["other hand"]["standing"] == False:
roundDone = False
if person["split"] > 1:
if person["third hand"]["hit"] == False and person["third hand"]["standing"] == False:
roundDone = False
if person["split"] > 2:
if person["fourth hand"]["hit"] == False and person["fourth hand"]["standing"] == False:
roundDone = False
return roundDone
# Loop of blackjack game rounds
async def blackjackLoop(self,channel,gameRound,gameID):
self.bot.log("Loop "+str(gameRound),str(channel.id))
with open("resources/games/oldImages/blackjack"+str(channel.id), "r") as f:
oldImage = await channel.fetch_message(int(f.read()))
new_message, allStanding, gamedone = self.blackjackContinue(str(channel.id))
if new_message != "":
self.bot.log(new_message,str(channel.id))
await channel.send(new_message)
if gamedone == False:
await oldImage.delete()
oldImage = await channel.send(file = discord.File("resources/games/blackjackTables/blackjackTable"+str(channel.id)+".png"))
with open("resources/games/oldImages/blackjack"+str(channel.id), "w") as f:
f.write(str(oldImage.id))
try:
if allStanding:
await asyncio.sleep(5)
else:
await asyncio.sleep(120)
except:
self.bot.log("Loop "+str(gameRound)+" interrupted (error code 1321)")
game = self.bot.database["blackjack games"].find_one({"_id":str(channel.id)})
if game != None:
realRound = game["round"]
realGameID = game["gameID"]
if gameRound == realRound and realGameID == gameID:
if gamedone == False:
self.bot.log("Loop "+str(gameRound)+" calling self.blackjackLoop()",str(channel.id))
await self.blackjackLoop(channel,gameRound+1,gameID)
else:
try:
new_message = self.blackjackFinish(str(channel.id))
except:
self.bot.log("Something fucked up (error code 1310)")
await channel.send(new_message)
else:
self.bot.log("Ending loop on round "+str(gameRound),str(channel.id))
else:
self.bot.log("Ending loop on round "+str(gameRound),str(channel.id))
# Returning current hi-lo value
async def hilo(self, ctx):
channel = ctx.channel_id
data = self.bot.database["hilo"].find_one({"_id":str(channel)})
if data != None:
hilo = str(data["hilo"])
else:
hilo = "0"
await ctx.send(f"Hi-lo value: {hilo}", hidden=True)
# Shuffles the blackjack deck
async def shuffle(self, ctx):
blackjackDecks = 4
channel = ctx.channel_id
self.blackjackShuffle(blackjackDecks,str(channel))
self.bot.log("Shuffling the blackjack deck...",str(channel))
await ctx.send("Shuffling the deck...")
# Tells you the amount of cards left
async def cards(self, ctx):
channel = ctx.channel_id
cardsLeft = 0
cards = self.bot.database["blackjack cards"].find_one({"_id":str(channel)})
if cards != None:
cardsLeft = len(cards["cards"])
decksLeft = round(cardsLeft/52,1)
await ctx.send(f"Cards left:\n{cardsLeft} cards, {decksLeft} decks", hidden=True)
class DrawBlackjack():
def __init__(self,bot):
self.bot = bot
self.BORDER = 100
self.PLACEMENT = [0,0]
self.ROTATION = 0
def drawImage(self,channel):
self.bot.log("Drawing blackjack table",channel)
game = self.bot.database["blackjack games"].find_one({"_id":channel})
fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', 50)
fntSmol = ImageFont.truetype('resources/fonts/futura-bold.ttf', 40)
self.BORDERSmol = int(self.BORDER/3.5)
table = Image.open("resources/games/blackjackTable.png")
self.PLACEMENT = [2,1,3,0,4]
textImage = ImageDraw.Draw(table)
hands = game["user hands"]
dealerBusted = game["dealer busted"]
dealerBlackjack = game["dealer blackjack"]
try:
if game["all standing"] == False:
dealerHand = self.drawHand(game["dealer hand"],True,False,False)
else:
dealerHand = self.drawHand(game["dealer hand"],False,dealerBusted,dealerBlackjack)
except:
self.bot.log("Error drawing dealer hand (error code 1341a)")
table.paste(dealerHand,(800-self.BORDERSmol,20-self.BORDERSmol),dealerHand)
for x in range(len(hands)):
key, value = list(hands.items())[x]
key = self.bot.databaseFuncs.getName(key)
#self.bot.log("Drawing "+key+"'s hand")
userHand = self.drawHand(value["hand"],False,value["busted"],value["blackjack"])
try:
if value["split"] == 3:
table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),280-self.BORDERSmol),userHand)
userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"])
table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),420-self.BORDERSmol),userOtherHand)
userThirdHand = self.drawHand(value["third hand"]["hand"],False,value["third hand"]["busted"],value["third hand"]["blackjack"])
table.paste(userThirdHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userThirdHand)
userFourthHand = self.drawHand(value["fourth hand"]["hand"],False,value["fourth hand"]["busted"],value["fourth hand"]["blackjack"])
table.paste(userFourthHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userFourthHand)
elif value["split"] == 2:
table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),420-self.BORDERSmol),userHand)
userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"])
table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userOtherHand)
userThirdHand = self.drawHand(value["third hand"]["hand"],False,value["third hand"]["busted"],value["third hand"]["blackjack"])
table.paste(userThirdHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userThirdHand)
elif value["split"] == 1:
table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userHand)
userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"])
table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userOtherHand)
else:
table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),680-self.BORDERSmol),userHand)
except:
self.bot.log("Error drawing player hands (error code 1341b)")
textWidth = fnt.getsize(key)[0]
if textWidth < 360:
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-3,1010-3),key,fill=(0,0,0), font=fnt)
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+3,1010-3),key,fill=(0,0,0), font=fnt)
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-3,1010+3),key,fill=(0,0,0), font=fnt)
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+3,1010+3),key,fill=(0,0,0), font=fnt)
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2),1005),key,fill=(255,255,255), font=fnt)
else:
textWidth = fntSmol.getsize(key)[0]
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-2,1020-2),key,fill=(0,0,0), font=fntSmol)
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+2,1020-2),key,fill=(0,0,0), font=fntSmol)
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-2,1020+2),key,fill=(0,0,0), font=fntSmol)
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+2,1020+2),key,fill=(0,0,0), font=fntSmol)
textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2),1015),key,fill=(255,255,255), font=fntSmol)
self.bot.log("Saving table image")
table.save("resources/games/blackjackTables/blackjackTable"+channel+".png")
return
def drawHand(self, hand, dealer, busted, blackjack):
self.bot.log("Drawing hand "+str(hand)+", "+str(busted)+", "+str(blackjack))
fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', 200)
fnt2 = ImageFont.truetype('resources/fonts/futura-bold.ttf', 120)
length = len(hand)
background = Image.new("RGBA", ((self.BORDER*2)+691+(125*(length-1)),(self.BORDER*2)+1065),(0,0,0,0))
textImage = ImageDraw.Draw(background)
if dealer:
img = Image.open("resources/games/cards/"+hand[0].upper()+".png")
#self.ROTATION = (random.randint(-20,20)/10.0)
img = img.rotate(self.ROTATION,expand = 1)
#self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)]
background.paste(img,(self.BORDER+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img)
img = Image.open("resources/games/cards/red_back.png")
#self.ROTATION = (random.randint(-20,20)/10.0)
img = img.rotate(self.ROTATION,expand = 1)
#self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)]
background.paste(img,(125+self.BORDER+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img)
else:
for x in range(length):
img = Image.open("resources/games/cards/"+hand[x].upper()+".png")
#self.ROTATION = (random.randint(-20,20)/10.0)
img = img.rotate(self.ROTATION,expand = 1)
#self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)]
background.paste(img,(self.BORDER+(x*125)+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img)
w, h = background.size
textHeight = 290+self.BORDER
#self.bot.log("Drawing busted/blackjack")
if busted:
textWidth = fnt.getsize("BUSTED")[0]
textImage.text((int(w/2)-int(textWidth/2)-10,textHeight+20-10),"BUSTED",fill=(0,0,0), font=fnt)
textImage.text((int(w/2)-int(textWidth/2)+10,textHeight+20-10),"BUSTED",fill=(0,0,0), font=fnt)
textImage.text((int(w/2)-int(textWidth/2)-10,textHeight+20+10),"BUSTED",fill=(0,0,0), font=fnt)
textImage.text((int(w/2)-int(textWidth/2)+10,textHeight+20+10),"BUSTED",fill=(0,0,0), font=fnt)
textImage.text((int(w/2)-int(textWidth/2)-5,textHeight-5),"BUSTED",fill=(255,255,255), font=fnt)
textImage.text((int(w/2)-int(textWidth/2)+5,textHeight-5),"BUSTED",fill=(255,255,255), font=fnt)
textImage.text((int(w/2)-int(textWidth/2)-5,textHeight+5),"BUSTED",fill=(255,255,225), font=fnt)
textImage.text((int(w/2)-int(textWidth/2)+5,textHeight+5),"BUSTED",fill=(255,255,255), font=fnt)
textImage.text((int(w/2)-int(textWidth/2),textHeight),"BUSTED",fill=(255,50,50), font=fnt)
elif blackjack:
textWidth = fnt2.getsize("BLACKJACK")[0]
textImage.text((int(w/2)-int(textWidth/2)-6,textHeight+20-6),"BLACKJACK",fill=(0,0,0), font=fnt2)
textImage.text((int(w/2)-int(textWidth/2)+6,textHeight+20-6),"BLACKJACK",fill=(0,0,0), font=fnt2)
textImage.text((int(w/2)-int(textWidth/2)-6,textHeight+20+6),"BLACKJACK",fill=(0,0,0), font=fnt2)
textImage.text((int(w/2)-int(textWidth/2)+6,textHeight+20+6),"BLACKJACK",fill=(0,0,0), font=fnt2)
textImage.text((int(w/2)-int(textWidth/2)-3,textHeight-3),"BLACKJACK",fill=(255,255,255), font=fnt2)
textImage.text((int(w/2)-int(textWidth/2)+3,textHeight-3),"BLACKJACK",fill=(255,255,255), font=fnt2)
textImage.text((int(w/2)-int(textWidth/2)-3,textHeight+3),"BLACKJACK",fill=(255,255,255), font=fnt2)
textImage.text((int(w/2)-int(textWidth/2)+3,textHeight+3),"BLACKJACK",fill=(255,255,255), font=fnt2)
textImage.text((int(w/2)-int(textWidth/2),textHeight),"BLACKJACK",fill=(155,123,0), font=fnt2)
#self.bot.log("Returning resized image")
return background.resize((int(w/3.5),int(h/3.5)),resample=Image.BILINEAR)

View File

@ -1,583 +0,0 @@
import random
import copy
import math
import discord
from PIL import Image, ImageDraw, ImageFont
class ConnectFour():
def __init__(self,bot):
self.bot = bot
self.draw = drawConnectFour(bot)
self.AISCORES = {
"middle": 3,
"two in a row": 10,
"three in a row": 50,
"enemy two in a row": -35,
"enemy three in a row": -200,
"enemy win": -10000,
"win": 10000,
"avoid losing": 100
}
self.ROWCOUNT = 6
self.COLUMNCOUNT = 7
# Starts the game
async def start(self, ctx, opponent):
await self.bot.defer(ctx)
user = f"#{ctx.author.id}"
channel = str(ctx.channel_id)
game = self.bot.database["connect 4 games"].find_one({"_id":channel})
startedGame = False
canStart = True
if game != None:
sendMessage = "There's already a connect 4 game going on in this channel"
logMessage = "There was already a game going on"
canStart = False
else:
if type(opponent) == int:
# Opponent is Gwendolyn
if opponent in range(1, 6):
difficulty = int(opponent)
diffText = f" with difficulty {difficulty}"
opponent = f"#{self.bot.user.id}"
else:
sendMessage = "Difficulty doesn't exist"
logMessage = "They tried to play against a difficulty that doesn't exist"
canStart = False
elif type(opponent) == discord.member.Member:
if opponent.bot:
# User has challenged a bot
if opponent == self.bot.user:
# It was Gwendolyn
difficulty = 3
diffText = f" with difficulty {difficulty}"
opponent = f"#{self.bot.user.id}"
else:
sendMessage = "You can't challenge a bot!"
logMessage = "They tried to challenge a bot"
canStart = False
else:
# Opponent is another player
if ctx.author != opponent:
opponent = f"#{opponent.id}"
difficulty = 5
diffText = ""
else:
sendMessage = "You can't play against yourself"
logMessage = "They tried to play against themself"
canStart = False
if canStart:
board = [[0 for _ in range(self.COLUMNCOUNT)] for _ in range(self.ROWCOUNT)]
players = [user, opponent]
random.shuffle(players)
newGame = {"_id":channel, "board": board, "winner":0,
"win direction":"", "win coordinates":[0, 0],
"players":players, "turn":0, "difficulty":difficulty}
self.bot.database["connect 4 games"].insert_one(newGame)
self.draw.drawImage(channel)
gwendoTurn = (players[0] == f"#{self.bot.user.id}")
startedGame = True
opponentName = self.bot.databaseFuncs.getName(opponent)
turnName = self.bot.databaseFuncs.getName(players[0])
startedText = f"Started game against {opponentName}{diffText}."
turnText = f"It's {turnName}'s turn"
sendMessage = f"{startedText} {turnText}"
logMessage = "They started a game"
self.bot.log(logMessage)
await ctx.send(sendMessage)
# Sets the whole game in motion
if startedGame:
filePath = f"resources/games/connect4Boards/board{ctx.channel_id}.png"
oldImage = await ctx.channel.send(file = discord.File(filePath))
with open(f"resources/games/oldImages/connectFour{ctx.channel_id}", "w") as f:
f.write(str(oldImage.id))
if gwendoTurn:
await self.connectFourAI(ctx)
else:
reactions = ["1","2","3","4","5","6","7"]
for reaction in reactions:
await oldImage.add_reaction(reaction)
# Places a piece at the lowest available point in a specific column
async def placePiece(self, ctx, user, column):
channel = str(ctx.channel.id)
game = self.bot.database["connect 4 games"].find_one({"_id":channel})
playerNumber = game["players"].index(user)+1
userName = self.bot.databaseFuncs.getName(user)
placedPiece = False
if game is None:
sendMessage = "There's no game in this channel"
logMessage = "There was no game in the channel"
else:
board = game["board"]
board = self.placeOnBoard(board, playerNumber, column)
if board is None:
sendMessage = "There isn't any room in that column"
logMessage = "There wasn't any room in the column"
else:
self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"board":board}})
turn = (game["turn"]+1)%2
self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"turn":turn}})
self.bot.log("Checking for win")
won, winDirection, winCoordinates = self.isWon(board)
if won != 0:
gameWon = True
self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"winner":won}})
self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"win direction":winDirection}})
self.bot.database["connect 4 games"].update_one({"_id":channel},
{"$set":{"win coordinates":winCoordinates}})
sendMessage = f"{userName} placed a piece in column {column+1} and won."
logMessage = f"{userName} won"
winAmount = int(game["difficulty"])**2+5
if game["players"][won-1] != f"#{self.bot.user.id}":
sendMessage += " Adding "+str(winAmount)+" GwendoBucks to their account"
elif 0 not in board[0]:
gameWon = True
sendMessage = "It's a draw!"
logMessage = "The game ended in a draw"
else:
gameWon = False
otherUserName = self.bot.databaseFuncs.getName(game["players"][turn])
sendMessage = f"{userName} placed a piece in column {column+1}. It's now {otherUserName}'s turn"
logMessage = "They placed the piece"
gwendoTurn = (game["players"][turn] == f"#{self.bot.user.id}")
placedPiece = True
await ctx.channel.send(sendMessage)
self.bot.log(logMessage)
if placedPiece:
self.draw.drawImage(channel)
with open(f"resources/games/oldImages/connectFour{channel}", "r") as f:
oldImage = await ctx.channel.fetch_message(int(f.read()))
if oldImage is not None:
await oldImage.delete()
else:
self.bot.log("The old image was already deleted")
filePath = f"resources/games/connect4Boards/board{channel}.png"
oldImage = await ctx.channel.send(file = discord.File(filePath))
if gameWon:
self.endGame(channel)
else:
with open(f"resources/games/oldImages/connectFour{channel}", "w") as f:
f.write(str(oldImage.id))
if gwendoTurn:
await self.connectFourAI(ctx)
else:
reactions = ["1","2","3","4","5","6","7"]
for reaction in reactions:
await oldImage.add_reaction(reaction)
# Returns a board where a piece has been placed in the column
def placeOnBoard(self, board, player, column):
placementX, placementY = -1, column
for x, line in enumerate(board):
if line[column] == 0:
placementX = x
board[placementX][placementY] = player
if placementX == -1:
return None
else:
return board
def endGame(self, channel):
game = self.bot.database["connect 4 games"].find_one({"_id":channel})
winner = game["winner"]
if winner != 0:
if game["players"][winner-1] != f"#{self.bot.user.id}":
difficulty = int(game["difficulty"])
reward = difficulty**2 + 5
self.bot.money.addMoney(game["players"][winner-1], reward)
self.bot.databaseFuncs.deleteGame("connect 4 games", channel)
# Parses command
async def surrender(self, ctx):
await self.bot.defer(ctx)
channel = str(ctx.channel_id)
game = self.bot.database["connect 4 games"].find_one({"_id":channel})
if f"#{ctx.author.id}" in game["players"]:
loserIndex = game["players"].index(f"#{ctx.author.id}")
winnerIndex = (loserIndex+1)%2
winnerID = game["players"][winnerIndex]
winnerName = self.bot.databaseFuncs.getName(winnerID)
sendMessage = f"{ctx.author.display_name} surrenders."
sendMessage += f" This means {winnerName} is the winner."
if winnerID != f"#{self.bot.user.id}":
difficulty = int(game["difficulty"])
reward = difficulty**2 + 5
sendMessage += f" Adding {reward} to their account"
await ctx.send(sendMessage)
with open(f"resources/games/oldImages/connectFour{channel}", "r") as f:
oldImage = await ctx.channel.fetch_message(int(f.read()))
if oldImage is not None:
await oldImage.delete()
else:
self.bot.log("The old image was already deleted")
self.endGame(channel)
else:
await ctx.send("You can't surrender when you're not a player")
# Checks if someone has won the game and returns the winner
def isWon(self, board):
won = 0
winDirection = ""
winCoordinates = [0,0]
for row in range(self.ROWCOUNT):
for place in range(self.COLUMNCOUNT):
if won == 0:
piecePlayer = board[row][place]
if piecePlayer != 0:
# Checks horizontal
if place <= self.COLUMNCOUNT-4:
pieces = [board[row][place+1],board[row][place+2],board[row][place+3]]
else:
pieces = [0]
if all(x == piecePlayer for x in pieces):
won = piecePlayer
winDirection = "h"
winCoordinates = [row,place]
# Checks vertical
if row <= self.ROWCOUNT-4:
pieces = [board[row+1][place],board[row+2][place],board[row+3][place]]
else:
pieces = [0]
if all(x == piecePlayer for x in pieces):
won = piecePlayer
winDirection = "v"
winCoordinates = [row,place]
# Checks right diagonal
if row <= self.ROWCOUNT-4 and place <= self.COLUMNCOUNT-4:
pieces = [board[row+1][place+1],board[row+2][place+2],board[row+3][place+3]]
else:
pieces = [0]
if all(x == piecePlayer for x in pieces):
won = piecePlayer
winDirection = "r"
winCoordinates = [row,place]
# Checks left diagonal
if row <= self.ROWCOUNT-4 and place >= 3:
pieces = [board[row+1][place-1],board[row+2][place-2],board[row+3][place-3]]
else:
pieces = [0]
if all(x == piecePlayer for x in pieces):
won = piecePlayer
winDirection = "l"
winCoordinates = [row,place]
return won, winDirection, winCoordinates
# Plays as the AI
async def connectFourAI(self, ctx):
channel = str(ctx.channel.id)
self.bot.log("Figuring out best move")
game = self.bot.database["connect 4 games"].find_one({"_id":channel})
board = game["board"]
player = game["players"].index(f"#{self.bot.user.id}")+1
difficulty = game["difficulty"]
scores = [-math.inf for _ in range(self.COLUMNCOUNT)]
for column in range(self.COLUMNCOUNT):
testBoard = copy.deepcopy(board)
testBoard = self.placeOnBoard(testBoard,player,column)
if testBoard != None:
scores[column] = await self.minimax(testBoard,difficulty,player%2+1,player,-math.inf,math.inf,False)
self.bot.log(f"Best score for column {column} is {scores[column]}")
possibleScores = scores.copy()
while (min(possibleScores) <= (max(possibleScores) - max(possibleScores)/10)) and len(possibleScores) != 1:
possibleScores.remove(min(possibleScores))
highest_score = random.choice(possibleScores)
bestColumns = [i for i, x in enumerate(scores) if x == highest_score]
placement = random.choice(bestColumns)
await self.placePiece(ctx, f"#{self.bot.user.id}", placement)
# Calculates points for a board
def AICalcPoints(self,board,player):
score = 0
otherPlayer = player%2+1
# Adds points for middle placement
# Checks horizontal
for row in range(self.ROWCOUNT):
if board[row][3] == player:
score += self.AISCORES["middle"]
rowArray = [int(i) for i in list(board[row])]
for place in range(self.COLUMNCOUNT-3):
window = rowArray[place:place+4]
score += self.evaluateWindow(window,player,otherPlayer)
# Checks Vertical
for column in range(self.COLUMNCOUNT):
columnArray = [int(i[column]) for i in list(board)]
for place in range(self.ROWCOUNT-3):
window = columnArray[place:place+4]
score += self.evaluateWindow(window,player,otherPlayer)
# Checks right diagonal
for row in range(self.ROWCOUNT-3):
for place in range(self.COLUMNCOUNT-3):
window = [board[row][place],board[row+1][place+1],board[row+2][place+2],board[row+3][place+3]]
score += self.evaluateWindow(window,player,otherPlayer)
for place in range(3,self.COLUMNCOUNT):
window = [board[row][place],board[row+1][place-1],board[row+2][place-2],board[row+3][place-3]]
score += self.evaluateWindow(window,player,otherPlayer)
## Checks if anyone has won
#won = isWon(board)[0]
## Add points if AI wins
#if won == player:
# score += self.AISCORES["win"]
return score
def evaluateWindow(self, window,player,otherPlayer):
if window.count(player) == 4:
return self.AISCORES["win"]
elif window.count(player) == 3 and window.count(0) == 1:
return self.AISCORES["three in a row"]
elif window.count(player) == 2 and window.count(0) == 2:
return self.AISCORES["two in a row"]
elif window.count(otherPlayer) == 4:
return self.AISCORES["enemy win"]
else:
return 0
async def minimax(self, board, depth, player , originalPlayer, alpha, beta, maximizingPlayer):
#terminal = ((0 not in board[0]) or (isWon(board)[0] != 0))
terminal = 0 not in board[0]
points = self.AICalcPoints(board,originalPlayer)
# The depth is how many moves ahead the computer checks. This value is the difficulty.
if depth == 0 or terminal or (points > 5000 or points < -6000):
return points
if maximizingPlayer:
value = -math.inf
for column in range(0,self.COLUMNCOUNT):
testBoard = copy.deepcopy(board)
testBoard = self.placeOnBoard(testBoard,player,column)
if testBoard != None:
evaluation = await self.minimax(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,False)
if evaluation < -9000: evaluation += self.AISCORES["avoid losing"]
value = max(value,evaluation)
alpha = max(alpha,evaluation)
if beta <= alpha: break
return value
else:
value = math.inf
for column in range(0,self.COLUMNCOUNT):
testBoard = copy.deepcopy(board)
testBoard = self.placeOnBoard(testBoard,player,column)
if testBoard != None:
evaluation = await self.minimax(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,True)
if evaluation < -9000: evaluation += self.AISCORES["avoid losing"]
value = min(value,evaluation)
beta = min(beta,evaluation)
if beta <= alpha: break
return value
class drawConnectFour():
def __init__(self, bot):
self.bot = bot
# Draws the whole thing
def drawImage(self, channel):
self.bot.log("Drawing connect four board")
game = self.bot.database["connect 4 games"].find_one({"_id":channel})
board = game["board"]
border = 40
gridBorder = 40
cornerSize = 300
boardOutlineSize = 10
pieceOutlineSize = 10
emptyOutlineSize = 0
bottomBorder = 110
exampleCircles = 100
w, h = 2800,2400
backgroundColor = (230,230,234,255)
boardOutlineColor = (0,0,0)
pieceOutlineColor = (244,244,248)
emptyOutlineColor = (0,0,0)
player1Color = (254,74,73)
player2Color = (254,215,102)
boardColor = (42,183,202)
placeSize = 300
white = (255,255,255,160)
winBarColor = (250,250,250,255)
fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', exampleCircles)
boardSize = [w-(2*(border+gridBorder)),h-(2*(border+gridBorder))]
placeGridSize = [math.floor(boardSize[0]/7),math.floor(boardSize[1]/6)]
pieceStartx = (border+gridBorder)+math.floor(placeGridSize[0]/2)-math.floor(placeSize/2)
pieceStarty = (border+gridBorder)+math.floor(placeGridSize[1]/2)-math.floor(placeSize/2)
if game["players"][0] == "Gwendolyn":
player1 = "Gwendolyn"
else:
player1 = self.bot.databaseFuncs.getName(game["players"][0])
if game["players"][1] == "Gwendolyn":
player2 = "Gwendolyn"
else:
player2 = self.bot.databaseFuncs.getName(game["players"][1])
background = Image.new("RGB", (w,h+bottomBorder),backgroundColor)
d = ImageDraw.Draw(background,"RGBA")
# This whole part was the easiest way to make a rectangle with rounded corners and an outline
# - Corners:
d.ellipse([(border,border),(border+cornerSize,border+cornerSize)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize)
d.ellipse([(w-(border+cornerSize),h-(border+cornerSize)),(w-border,h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize)
d.ellipse([(border,h-(border+cornerSize)),(border+cornerSize,h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize)
d.ellipse([(w-(border+cornerSize),border),(w-border,border+cornerSize)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize)
# - Rectangle:
d.rectangle([(border+math.floor(cornerSize/2),border),(w-(border+math.floor(cornerSize/2)),h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize)
d.rectangle([(border,border+math.floor(cornerSize/2)),(w-border,h-(border+math.floor(cornerSize/2)))],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize)
# - Removing outline on the inside:
d.rectangle([(border+math.floor(cornerSize/2),border+math.floor(cornerSize/2)),(w-(border+math.floor(cornerSize/2)),h-(border+math.floor(cornerSize/2)))],fill=boardColor)
d.rectangle([(border+math.floor(cornerSize/2),border+boardOutlineSize),(w-(border+math.floor(cornerSize/2)),h-(border+boardOutlineSize))],fill=boardColor)
d.rectangle([(border+boardOutlineSize,border+math.floor(cornerSize/2)),(w-(border+boardOutlineSize),h-(border+math.floor(cornerSize/2)))],fill=boardColor)
for x, line in enumerate(board):
for y, piece in enumerate(line):
if piece == 1:
pieceColor = player1Color
outlineWidth = pieceOutlineSize
outlineColor = pieceOutlineColor
elif piece == 2:
pieceColor = player2Color
outlineWidth = pieceOutlineSize
outlineColor = pieceOutlineColor
else:
pieceColor = backgroundColor
outlineWidth = emptyOutlineSize
outlineColor = emptyOutlineColor
startx = pieceStartx + placeGridSize[0]*y
starty = pieceStarty + placeGridSize[1]*x
d.ellipse([(startx,starty),(startx+placeSize,starty+placeSize)],fill=pieceColor,outline=outlineColor,width=outlineWidth)
if game["winner"] != 0:
coordinates = game["win coordinates"]
startx = border + placeGridSize[0]*coordinates[1] + gridBorder
starty = border + placeGridSize[1]*coordinates[0] + gridBorder
a = (placeGridSize[0]*4-gridBorder-border)**2
b = (placeGridSize[1]*4-gridBorder-border)**2
diagonalLength = (math.sqrt(a+b))/placeGridSize[0]
diagonalAngle = math.degrees(math.atan(placeGridSize[1]/placeGridSize[0]))
if game["win direction"] == "h":
winBar = Image.new("RGBA",(placeGridSize[0]*4,placeGridSize[1]),(0,0,0,0))
winD = ImageDraw.Draw(winBar)
winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white)
winD.ellipse([((placeGridSize[0]*3),0),(placeGridSize[0]*4,placeGridSize[1])],fill=white)
winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*3.5),placeGridSize[1])],fill=white)
elif game["win direction"] == "v":
winBar = Image.new("RGBA",(placeGridSize[0],placeGridSize[1]*4),(0,0,0,0))
winD = ImageDraw.Draw(winBar)
winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white)
winD.ellipse([(0,(placeGridSize[1]*3)),(placeGridSize[0],placeGridSize[1]*4)],fill=white)
winD.rectangle([0,(int(placeGridSize[1]*0.5)),(placeGridSize[0],int(placeGridSize[1]*3.5))],fill=white)
elif game["win direction"] == "r":
winBar = Image.new("RGBA",(int(placeGridSize[0]*diagonalLength),placeGridSize[1]),(0,0,0,0))
winD = ImageDraw.Draw(winBar)
winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white)
winD.ellipse([((placeGridSize[0]*(diagonalLength-1)),0),(placeGridSize[0]*diagonalLength,placeGridSize[1])],fill=white)
winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*(diagonalLength-0.5)),placeGridSize[1])],fill=white)
winBar = winBar.rotate(-diagonalAngle,expand=1)
startx -= 90
starty -= 100
elif game["win direction"] == "l":
winBar = Image.new("RGBA",(int(placeGridSize[0]*diagonalLength),placeGridSize[1]),(0,0,0,0))
winD = ImageDraw.Draw(winBar)
winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white)
winD.ellipse([((placeGridSize[0]*(diagonalLength-1)),0),(placeGridSize[0]*diagonalLength,placeGridSize[1])],fill=white)
winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*(diagonalLength-0.5)),placeGridSize[1])],fill=white)
winBar = winBar.rotate(diagonalAngle,expand=1)
startx -= placeGridSize[0]*3 + 90
starty -= gridBorder + 60
mask = winBar.copy()#.convert("L")
#mask.putalpha(128)
#mask.save("test.png")
winBarImage = Image.new("RGBA",mask.size,color=winBarColor)
background.paste(winBarImage,(startx,starty),mask)
# Bottom
textPadding = 20
exampleHeight = h - border + int((bottomBorder+border)/2) - int(exampleCircles/2)
d.ellipse([(border,exampleHeight),(border+exampleCircles),(exampleHeight+exampleCircles)],fill=player1Color,outline=boardOutlineColor,width=3)
d.text((border+exampleCircles+textPadding,exampleHeight),player1,font=fnt,fill=(0,0,0))
textWidth = fnt.getsize(player2)[0]
d.ellipse([(w-border-exampleCircles-textWidth-textPadding,exampleHeight),(w-border-textWidth-textPadding),(exampleHeight+exampleCircles)],fill=player2Color,outline=boardOutlineColor,width=3)
d.text((w-border-textWidth,exampleHeight),player2,font=fnt,fill=(0,0,0))
background.save("resources/games/connect4Boards/board"+channel+".png")

View File

@ -1,17 +0,0 @@
from .invest import Invest
from .trivia import Trivia
from .blackjack import Blackjack
from .connectFour import ConnectFour
from .hangman import Hangman
from .hex import HexGame
class Games():
def __init__(self, bot):
self.bot = bot
self.invest = Invest(bot)
self.trivia = Trivia(bot)
self.blackjack = Blackjack(bot)
self.connectFour = ConnectFour(bot)
self.hangman = Hangman(bot)
self.hex = HexGame(bot)

View File

@ -1,407 +0,0 @@
import json, urllib, datetime, string, discord
import math, random
from PIL import ImageDraw, Image, ImageFont
class Hangman():
def __init__(self,bot):
self.bot = bot
self.draw = DrawHangman(bot)
self.APIURL = "https://api.wordnik.com/v4/words.json/randomWords?hasDictionaryDef=true&minCorpusCount=5000&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=3&maxLength=11&limit=1&api_key="
async def start(self, ctx):
await self.bot.defer(ctx)
channel = str(ctx.channel_id)
user = f"#{ctx.author.id}"
game = self.bot.database["hangman games"].find_one({"_id":channel})
userName = self.bot.databaseFuncs.getName(user)
startedGame = False
if game == None:
apiKey = self.bot.credentials.wordnikKey
word = "-"
while "-" in word or "." in word:
with urllib.request.urlopen(self.APIURL+apiKey) as p:
word = list(json.load(p)[0]["word"].upper())
self.bot.log("Found the word \""+"".join(word)+"\"")
guessed = [False] * len(word)
gameID = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
newGame = {"_id":channel,"player" : user,"guessed letters" : [],"word" : word,"game ID" : gameID,"misses" : 0,"guessed" : guessed}
self.bot.database["hangman games"].insert_one(newGame)
remainingLetters = list(string.ascii_uppercase)
self.draw.drawImage(channel)
logMessage = "Game started"
sendMessage = f"{userName} started game of hangman."
startedGame = True
else:
logMessage = "There was already a game going on"
sendMessage = "There's already a Hangman game going on in the channel"
self.bot.log(logMessage)
await ctx.send(sendMessage)
if startedGame:
filePath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png"
newImage = await ctx.channel.send(file = discord.File(filePath))
blankMessage = await ctx.channel.send("_ _")
reactionMessages = {newImage : remainingLetters[:15], blankMessage : remainingLetters[15:]}
oldMessages = f"{newImage.id}\n{blankMessage.id}"
with open(f"resources/games/oldImages/hangman{channel}", "w") as f:
f.write(oldMessages)
for message, letters in reactionMessages.items():
for letter in letters:
emoji = chr(ord(letter)+127397)
await message.add_reaction(emoji)
async def stop(self, ctx):
channel = str(ctx.channel.id)
game = self.bot.database["hangman games"].find_one({"_id": channel})
if game is None:
await ctx.send("There's no game going on")
elif f"#{ctx.author.id}" != game["player"]:
await ctx.send("You can't end a game you're not in")
else:
self.bot.database["hangman games"].delete_one({"_id":channel})
with open(f"resources/games/oldImages/hangman{channel}", "r") as f:
messages = f.read().splitlines()
for message in messages:
oldMessage = await ctx.channel.fetch_message(int(message))
self.bot.log("Deleting old message")
await oldMessage.delete()
await ctx.send("Game stopped")
async def guess(self, message, user, guess):
channel = str(message.channel.id)
game = self.bot.database["hangman games"].find_one({"_id":channel})
gameExists = (game != None)
singleLetter = (len(guess) == 1 and guess.isalpha())
newGuess = (guess not in game["guessed letters"])
validGuess = (gameExists and singleLetter and newGuess)
if validGuess:
self.bot.log("Guessed the letter")
correctGuess = 0
for x, letter in enumerate(game["word"]):
if guess == letter:
correctGuess += 1
self.bot.database["hangman games"].update_one({"_id":channel},{"$set":{"guessed."+str(x):True}})
if correctGuess == 0:
self.bot.database["hangman games"].update_one({"_id":channel},{"$inc":{"misses":1}})
self.bot.database["hangman games"].update_one({"_id":channel},{"$push":{"guessed letters":guess}})
remainingLetters = list(string.ascii_uppercase)
game = self.bot.database["hangman games"].find_one({"_id":channel})
for letter in game["guessed letters"]:
remainingLetters.remove(letter)
if correctGuess == 1:
sendMessage = f"Guessed {guess}. There was 1 {guess} in the word."
else:
sendMessage = f"Guessed {guess}. There were {correctGuess} {guess}s in the word."
self.draw.drawImage(channel)
if game["misses"] == 6:
self.bot.database["hangman games"].delete_one({"_id":channel})
sendMessage += " You've guessed wrong six times and have lost the game."
remainingLetters = []
elif all(i == True for i in game["guessed"]):
self.bot.database["hangman games"].delete_one({"_id":channel})
self.bot.money.addMoney(user,15)
sendMessage += " You've guessed the word! Congratulations! Adding 15 GwendoBucks to your account"
remainingLetters = []
await message.channel.send(sendMessage)
with open(f"resources/games/oldImages/hangman{channel}", "r") as f:
oldMessageIDs = f.read().splitlines()
for oldID in oldMessageIDs:
oldMessage = await message.channel.fetch_message(int(oldID))
self.bot.log("Deleting old message")
await oldMessage.delete()
filePath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png"
newImage = await message.channel.send(file = discord.File(filePath))
if len(remainingLetters) > 0:
if len(remainingLetters) > 15:
blankMessage = await message.channel.send("_ _")
reactionMessages = {newImage : remainingLetters[:15], blankMessage : remainingLetters[15:]}
else:
blankMessage = ""
reactionMessages = {newImage : remainingLetters}
if blankMessage != "":
oldMessages = f"{newImage.id}\n{blankMessage.id}"
else:
oldMessages = str(newImage.id)
with open(f"resources/games/oldImages/hangman{channel}", "w") as f:
f.write(oldMessages)
for message, letters in reactionMessages.items():
for letter in letters:
emoji = chr(ord(letter)+127397)
await message.add_reaction(emoji)
class DrawHangman():
def __init__(self,bot):
self.bot = bot
self.CIRCLESIZE = 120
self.LINEWIDTH = 12
self.BODYSIZE =210
self.LIMBSIZE = 60
self.ARMPOSITION = 60
self.MANX = (self.LIMBSIZE*2)+self.LINEWIDTH*4
self.MANY = (self.CIRCLESIZE+self.BODYSIZE+self.LIMBSIZE)+self.LINEWIDTH*4
self.LETTERLINELENGTH = 90
self.LETTERLINEDISTANCE = 30
self.GALLOWX, self.GALLOWY = 360,600
self.GOLDENRATIO = 1-(1 / ((1 + 5 ** 0.5) / 2))
LETTERSIZE = 75
TEXTSIZE = 70
self.FONT = ImageFont.truetype('resources/fonts/comic-sans-bold.ttf', LETTERSIZE)
self.SMALLFONT = ImageFont.truetype('resources/fonts/comic-sans-bold.ttf', TEXTSIZE)
def calcDeviance(self,preDev,preDevAcc,posChange,maxmin,maxAcceleration):
devAcc = preDevAcc + random.uniform(-posChange,posChange)
if devAcc > maxmin * maxAcceleration: devAcc = maxmin * maxAcceleration
elif devAcc < -maxmin * maxAcceleration: devAcc = -maxmin * maxAcceleration
dev = preDev + devAcc
if dev > maxmin: dev = maxmin
elif dev < -maxmin: dev = -maxmin
return dev, devAcc
def badCircle(self):
background = Image.new("RGBA",(self.CIRCLESIZE+(self.LINEWIDTH*3),self.CIRCLESIZE+(self.LINEWIDTH*3)),color=(0,0,0,0))
d = ImageDraw.Draw(background,"RGBA")
middle = (self.CIRCLESIZE+(self.LINEWIDTH*3))/2
devx = 0
devy = 0
devAccx = 0
devAccy = 0
start = random.randint(-100,-80)
degreesAmount = 360 + random.randint(-10,30)
for degree in range(degreesAmount):
devx, devAccx = self.calcDeviance(devx,devAccx,self.LINEWIDTH/100,self.LINEWIDTH,0.03)
devy, devAccy = self.calcDeviance(devy,devAccy,self.LINEWIDTH/100,self.LINEWIDTH,0.03)
x = middle + (math.cos(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devx
y = middle + (math.sin(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devy
d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255))
return background
def badLine(self, length, rotated = False):
if rotated:
w, h = length+self.LINEWIDTH*3, self.LINEWIDTH*3
else:
w, h = self.LINEWIDTH*3,length+self.LINEWIDTH*3
background = Image.new("RGBA",(w,h),color=(0,0,0,0))
d = ImageDraw.Draw(background,"RGBA")
devx = random.randint(-int(self.LINEWIDTH/3),int(self.LINEWIDTH/3))
devy = 0
devAccx = 0
devAccy = 0
for pixel in range(length):
devx, devAccx = self.calcDeviance(devx,devAccx,self.LINEWIDTH/1000,self.LINEWIDTH,0.004)
devy, devAccy = self.calcDeviance(devy,devAccy,self.LINEWIDTH/1000,self.LINEWIDTH,0.004)
if rotated:
x = self.LINEWIDTH + pixel + devx
y = self.LINEWIDTH + devy
else:
x = self.LINEWIDTH + devx
y = self.LINEWIDTH + pixel + devy
d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255))
return background
def drawMan(self, misses):
background = Image.new("RGBA",(self.MANX,self.MANY),color=(0,0,0,0))
if misses >= 1:
head = self.badCircle()
background.paste(head,(int((self.MANX-(self.CIRCLESIZE+(self.LINEWIDTH*3)))/2),0),head)
if misses >= 2:
body = self.badLine(self.BODYSIZE)
background.paste(body,(int((self.MANX-(self.LINEWIDTH*3))/2),self.CIRCLESIZE),body)
if misses >= 3:
limbs = random.sample(["rl","ll","ra","la"],min(misses-2,4))
else: limbs = []
for limb in limbs:
if limb == "ra":
limbDrawing = self.badLine(self.LIMBSIZE,True)
rotation = random.randint(-45,45)
xpos = int((self.MANX-(self.LINEWIDTH*3))/2)
rotationCompensation = min(-int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0)
ypos = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation
limbDrawing = limbDrawing.rotate(rotation,expand=1)
background.paste(limbDrawing,(xpos,ypos),limbDrawing)
elif limb == "la":
limbDrawing = self.badLine(self.LIMBSIZE,True)
rotation = random.randint(-45,45)
xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LIMBSIZE
rotationCompensation = min(int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0)
ypos = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation
limbDrawing = limbDrawing.rotate(rotation,expand=1)
background.paste(limbDrawing,(xpos,ypos),limbDrawing)
elif limb == "rl":
limbDrawing = self.badLine(self.LIMBSIZE,True)
rotation = random.randint(-15,15)
xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LINEWIDTH
ypos = self.CIRCLESIZE+self.BODYSIZE-self.LINEWIDTH
limbDrawing = limbDrawing.rotate(rotation-45,expand=1)
background.paste(limbDrawing,(xpos,ypos),limbDrawing)
elif limb == "ll":
limbDrawing = self.badLine(self.LIMBSIZE,True)
rotation = random.randint(-15,15)
limbDrawing = limbDrawing.rotate(rotation+45,expand=1)
xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-limbDrawing.size[0]+self.LINEWIDTH*3
ypos = self.CIRCLESIZE+self.BODYSIZE
background.paste(limbDrawing,(xpos,ypos),limbDrawing)
return background
def badText(self, text, big, color=(0,0,0,255)):
if big: font = self.FONT
else: font = self.SMALLFONT
w, h = font.getsize(text)
img = Image.new("RGBA",(w,h),color=(0,0,0,0))
d = ImageDraw.Draw(img,"RGBA")
d.text((0,0),text,font=font,fill=color)
return img
def drawGallows(self):
background = Image.new("RGBA",(self.GALLOWX,self.GALLOWY),color=(0,0,0,0))
bottomLine = self.badLine(int(self.GALLOWX*0.75),True)
background.paste(bottomLine,(int(self.GALLOWX*0.125),self.GALLOWY-(self.LINEWIDTH*4)),bottomLine)
lineTwo = self.badLine(self.GALLOWY-self.LINEWIDTH*6)
background.paste(lineTwo,(int(self.GALLOWX*(0.75*self.GOLDENRATIO)),self.LINEWIDTH*2),lineTwo)
topLine = self.badLine(int(self.GALLOWY*0.30),True)
background.paste(topLine,(int(self.GALLOWX*(0.75*self.GOLDENRATIO))-self.LINEWIDTH,self.LINEWIDTH*3),topLine)
lastLine = self.badLine(int(self.GALLOWY*0.125))
background.paste(lastLine,((int(self.GALLOWX*(0.75*self.GOLDENRATIO))+int(self.GALLOWY*0.30)-self.LINEWIDTH),self.LINEWIDTH*3),lastLine)
return background
def drawLetterLines(self, word,guessed,misses):
letterLines = Image.new("RGBA",((self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)*len(word),self.LETTERLINELENGTH+self.LINEWIDTH*3),color=(0,0,0,0))
for x, letter in enumerate(word):
line = self.badLine(self.LETTERLINELENGTH,True)
letterLines.paste(line,(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE),self.LETTERLINELENGTH),line)
if guessed[x]:
letterDrawing = self.badText(letter,True)
letterWidth = self.FONT.getsize(letter)[0]
letterx = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2))
letterLines.paste(letterDrawing,(letterx,0),letterDrawing)
elif misses == 6:
letterDrawing = self.badText(letter,True,(242,66,54))
letterWidth = self.FONT.getsize(letter)[0]
letterx = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2))
letterLines.paste(letterDrawing,(letterx,0),letterDrawing)
return letterLines
def shortestDist(self,positions,newPosition):
shortestDist = math.inf
x, y = newPosition
for i, j in positions:
xdist = abs(i-x)
ydist = abs(j-y)
dist = math.sqrt(xdist**2+ydist**2)
if shortestDist > dist: shortestDist = dist
return shortestDist
def drawMisses(self,guesses,word):
background = Image.new("RGBA",(600,400),color=(0,0,0,0))
pos = []
for guess in guesses:
if guess not in word:
placed = False
while placed == False:
letter = self.badText(guess,True)
w, h = self.FONT.getsize(guess)
x = random.randint(0,600-w)
y = random.randint(0,400-h)
if self.shortestDist(pos,(x,y)) > 70:
pos.append((x,y))
background.paste(letter,(x,y),letter)
placed = True
return background
def drawImage(self,channel):
self.bot.log("Drawing hangman image", channel)
game = self.bot.database["hangman games"].find_one({"_id":channel})
random.seed(game["game ID"])
background = Image.open("resources/paper.jpg")
try:
gallow = self.drawGallows()
except:
self.bot.log("Error drawing gallows (error code 1711)")
try:
man = self.drawMan(game["misses"])
except:
self.bot.log("Error drawing stick figure (error code 1712)")
random.seed(game["game ID"])
try:
letterLines = self.drawLetterLines(game["word"],game["guessed"],game["misses"])
except:
self.bot.log("error drawing letter lines (error code 1713)")
random.seed(game["game ID"])
try:
misses = self.drawMisses(game["guessed letters"],game["word"])
except:
self.bot.log("Error drawing misses (error code 1714)")
background.paste(gallow,(100,100),gallow)
background.paste(man,(300,210),man)
background.paste(letterLines,(120,840),letterLines)
background.paste(misses,(600,150),misses)
missesText = self.badText("MISSES",False)
missesTextWidth = missesText.size[0]
background.paste(missesText,(850-int(missesTextWidth/2),50),missesText)
background.save("resources/games/hangmanBoards/hangmanBoard"+channel+".png")

View File

@ -1,149 +0,0 @@
import discord
class Invest():
def __init__(self, bot):
self.bot = bot
def getPrice(self, symbol : str):
res = self.bot.finnhubClient.quote(symbol.upper())
if res == {}:
return 0
else:
return int(res["c"] * 100)
def getPortfolio(self, user : str):
userInvestments = self.bot.database["investments"].find_one({"_id":user})
if userInvestments in [None,{}]:
return f"{self.bot.databaseFuncs.getName(user)} does not have a stock portfolio."
else:
portfolio = f"**Stock portfolio for {self.bot.databaseFuncs.getName(user)}**"
for key, value in list(userInvestments["investments"].items()):
purchaseValue = value["purchased for"]
currentValue = int((self.getPrice(key) / value["value at purchase"]) * value["purchased"])
if purchaseValue == "?":
portfolio += f"\n**{key}**: ___{str(currentValue)} GwendoBucks___"
else:
portfolio += f"\n**{key}**: ___{str(currentValue)} GwendoBucks___ (purchased for {str(purchaseValue)})"
return portfolio
def buyStock(self, user : str, stock : str, buyAmount : int):
if buyAmount >= 100:
if self.bot.money.checkBalance(user) >= buyAmount:
stockPrice = self.getPrice(stock)
if stockPrice > 0:
userInvestments = self.bot.database["investments"].find_one({"_id":user})
self.bot.money.addMoney(user,-1*buyAmount)
stock = stock.upper()
if userInvestments != None:
userInvestments = userInvestments["investments"]
if stock in userInvestments:
value = userInvestments[stock]
newAmount = int((stockPrice / value["value at purchase"]) * value["purchased"]) + buyAmount
self.bot.database["investments"].update_one({"_id":user},
{"$set":{"investments."+stock+".value at purchase" : stockPrice}})
self.bot.database["investments"].update_one({"_id":user},
{"$set":{"investments."+stock+".purchased" : newAmount}})
if value["purchased for"] != "?":
self.bot.database["investments"].update_one({"_id":user},
{"$set":{"investments."+stock+".purchased for" : buyAmount}})
else:
self.bot.database["investments"].update_one({"_id":user},
{"$set":{"investments."+stock : {"purchased" : buyAmount, "value at purchase" : stockPrice,
"purchased for" : buyAmount}}})
else:
newUser = {"_id":user,"investments":{stock : {"purchased" : buyAmount, "value at purchase" : stockPrice, "purchased for" : buyAmount}}}
self.bot.database["investments"].insert_one(newUser)
return f"{self.bot.databaseFuncs.getName(user)} bought {buyAmount} GwendoBucks worth of {stock} stock"
else:
return f"{stock} is not traded on the american market."
else:
return "You don't have enough money for that"
else:
return "You cannot buy stocks for less than 100 GwendoBucks"
def sellStock(self, user : str, stock : str, sellAmount : int):
if sellAmount > 0:
userInvestments = self.bot.database["investments"].find_one({"_id":user})["investments"]
stock = stock.upper()
if userInvestments != None and stock in userInvestments:
value = userInvestments[stock]
stockPrice = self.getPrice(stock)
self.bot.database["investments"].update_one({"_id":user},
{"$set":{"investments."+stock+".purchased" :
int((stockPrice / value["value at purchase"]) * value["purchased"])}})
self.bot.database["investments"].update_one({"_id":user},
{"$set":{"investments."+stock+".value at purchase" : stockPrice}})
if value["purchased"] >= sellAmount:
self.bot.money.addMoney(user,sellAmount)
if sellAmount < value["purchased"]:
self.bot.database["investments"].update_one({"_id":user},
{"$inc":{"investments."+stock+".purchased" : -sellAmount}})
self.bot.database["investments"].update_one({"_id":user},
{"$set":{"investments."+stock+".purchased for" : "?"}})
else:
self.bot.database["investments"].update_one({"_id":user},
{"$unset":{"investments."+stock:""}})
return f"{self.bot.databaseFuncs.getName(user)} sold {sellAmount} GwendoBucks worth of {stock} stock"
else:
return f"You don't have enough {stock} stocks to do that"
else:
return f"You don't have any {stock} stock"
else:
return "no"
async def parseInvest(self, ctx, parameters):
await self.bot.defer(ctx)
user = f"#{ctx.author.id}"
if parameters.startswith("check"):
commands = parameters.split(" ")
if len(commands) == 1:
response = self.getPortfolio(user)
else:
price = self.getPrice(commands[1])
if price == 0:
response = f"{commands[1].upper()} is not traded on the american market."
else:
price = f"{price:,}".replace(",",".")
response = f"The current {commands[1].upper()} stock is valued at **{price}** GwendoBucks"
elif parameters.startswith("buy"):
commands = parameters.split(" ")
if len(commands) == 3:
response = self.buyStock(user,commands[1],int(commands[2]))
else:
response = "You must give both a stock name and an amount of gwendobucks you wish to spend."
elif parameters.startswith("sell"):
commands = parameters.split(" ")
if len(commands) == 3:
try:
response = self.sellStock(user,commands[1],int(commands[2]))
except:
response = "The command must be given as \"/invest sell [stock] [amount of GwendoBucks to sell stocks for]\""
else:
response = "You must give both a stock name and an amount of GwendoBucks you wish to sell stocks for."
else:
response = "Incorrect parameters"
if response.startswith("**"):
responses = response.split("\n")
em = discord.Embed(title=responses[0],description="\n".join(responses[1:]),colour=0x00FF00)
await ctx.send(embed=em)
else:
await ctx.send(response)

View File

@ -1,70 +0,0 @@
class Money():
def __init__(self, bot):
self.bot = bot
self.database = bot.database
# Returns the account balance for a user
def checkBalance(self, user):
self.bot.log("checking "+user+"'s account balance")
userData = self.database["users"].find_one({"_id":user})
if userData != None:
return userData["money"]
else: return 0
async def sendBalance(self, ctx):
await self.bot.defer(ctx)
response = self.checkBalance("#"+str(ctx.author.id))
if response == 1:
new_message = ctx.author.display_name + " has " + str(response) + " GwendoBuck"
else:
new_message = ctx.author.display_name + " has " + str(response) + " GwendoBucks"
await ctx.send(new_message)
# Adds money to the account of a user
def addMoney(self,user,amount):
self.bot.log("adding "+str(amount)+" to "+user+"'s account")
userData = self.database["users"].find_one({"_id":user})
if userData != None:
self.database["users"].update_one({"_id":user},{"$inc":{"money":amount}})
else:
self.database["users"].insert_one({"_id":user,"user name":self.bot.databaseFuncs.getName(user),"money":amount})
# Transfers money from one user to another
async def giveMoney(self, ctx, user, amount):
await self.bot.defer(ctx)
username = user.display_name
if self.bot.databaseFuncs.getID(username) == None:
async for member in ctx.guild.fetch_members(limit=None):
if member.display_name.lower() == username.lower():
username = member.display_name
userID = "#" + str(member.id)
newUser = {"_id":userID,"user name":username,"money":0}
self.bot.database["users"].insert_one(newUser)
userData = self.database["users"].find_one({"_id":f"#{ctx.author.id}"})
targetUser = self.bot.databaseFuncs.getID(username)
if amount > 0:
if targetUser != None:
if userData != None:
if userData["money"] >= amount:
self.addMoney(f"#{ctx.author.id}",-1 * amount)
self.addMoney(targetUser,amount)
await ctx.send(f"Transferred {amount} GwendoBucks to {username}")
else:
self.bot.log("They didn't have enough GwendoBucks")
await ctx.send("You don't have that many GwendoBucks")
else:
self.bot.log("They didn't have enough GwendoBucks")
await ctx.send("You don't have that many GwendoBuck")
else:
self.bot.log("They weren't in the system")
await ctx.send("The target doesn't exist")
else:
self.bot.log("They tried to steal")
await ctx.send("Yeah, no. You can't do that")

View File

@ -1,118 +0,0 @@
import json
import urllib
import random
import asyncio
class Trivia():
def __init__(self, bot):
self.bot = bot
# Starts a game of trivia. Downloads a question with answers, shuffles the wrong answers with the
# correct answer and returns the questions and answers. Also saves the question in the games.json file.
def triviaStart(self, channel : str):
question = self.bot.database["trivia questions"].find_one({"_id":channel})
self.bot.log("Trying to find a trivia question for "+channel)
if question == None:
with urllib.request.urlopen("https://opentdb.com/api.php?amount=10&type=multiple") as response:
data = json.loads(response.read())
self.bot.log("Found the question \""+data["results"][0]["question"]+"\"")
answers = data["results"][0]["incorrect_answers"]
answers.append(data["results"][0]["correct_answer"])
random.shuffle(answers)
correctAnswer = answers.index(data["results"][0]["correct_answer"]) + 97
self.bot.database["trivia questions"].insert_one({"_id":channel,"answer" : str(chr(correctAnswer)),"players" : {}})
replacements = {"&#039;": "\'",
"&quot;": "\"",
"&ldquo;": "\"",
"&rdquo;": "\"",
"&eacute;": "é"}
question = data["results"][0]["question"]
for key, value in replacements.items():
question = question.replace(key,value)
for answer in answers:
for key, value in replacements.items():
answer = answer.replace(key,value)
return question, answers, correctAnswer
else:
self.bot.log("There was already a trivia question for that channel (error code 1106)")
return "There's already a trivia question going on. Try again in like, a minute (error code 1106)", "", ""
# Lets players answer a trivia question
def triviaAnswer(self, user : str, channel : str, command : str):
question = self.bot.database["trivia questions"].find_one({"_id":channel})
if command in ["a","b","c","d"]:
if question != None:
if user not in question["players"]:
self.bot.log(user+" answered the question in "+channel)
self.bot.database["trivia questions"].update_one({"_id":channel},{"$set":{"players."+user : command}})
return "Locked in "+user+"'s answer"
else:
self.bot.log(user+" has already answered this question (error code 1105)")
return user+" has already answered this question (error code 1105)"
else:
self.bot.log("There's no question right now (error code 1104)")
return "There's no question right now (error code 1104)"
else:
self.bot.log("I didn't quite understand that (error code 1103)")
return "I didn't quite understand that (error code 1103)"
# Adds 1 GwendoBuck to each player that got the question right and deletes question from games.json.
def triviaCountPoints(self, channel : str):
question = self.bot.database["trivia questions"].find_one({"_id":channel})
self.bot.log("Counting points for question in "+channel)
if question != None:
for player, answer in question["players"].items():
if answer == question["answer"]:
self.bot.money.addMoney(player,1)
else:
self.bot.log("Couldn't find the question (error code 1102)")
return None
async def triviaParse(self, ctx, answer):
await self.bot.defer(ctx)
if answer == "":
question, options, correctAnswer = self.triviaStart(str(ctx.channel_id))
if options != "":
results = "**"+question+"**\n"
for x, option in enumerate(options):
results += chr(x+97) + ") "+option+"\n"
await ctx.send(results)
await asyncio.sleep(60)
self.triviaCountPoints(str(ctx.channel_id))
self.bot.databaseFuncs.deleteGame("trivia questions",str(ctx.channel_id))
self.bot.log("Time's up for the trivia question",str(ctx.channel_id))
await ctx.send("Time's up The answer was \"*"+chr(correctAnswer)+") "+options[correctAnswer-97]+"*\". Anyone who answered that has gotten 1 GwendoBuck")
else:
await ctx.send(question, hidden=True)
elif answer in ["a","b","c","d"]:
response = self.triviaAnswer("#"+str(ctx.author.id), str(ctx.channel_id), answer)
if response.startswith("Locked in "):
await ctx.send(f"{ctx.author.display_name} answered **{answer}**")
else:
await ctx.send(response)
else:
self.bot.log("I didn't understand that (error code 1101)",str(ctx.channel_id))
await ctx.send("I didn't understand that (error code 1101)")

View File

@ -1,413 +0,0 @@
import requests, imdb, discord, json, math, time, asyncio
class BedreNetflix():
def __init__(self,bot):
self.bot = bot
ip = ["localhost", "192.168.0.40"][self.bot.options.testing]
self.radarrURL = "http://"+ip+":7878/api/v3/"
self.sonarrURL = "http://"+ip+":8989/api/"
self.qbittorrentURL = "http://"+ip+":8080/api/v2/"
self.moviePath = "/media/plex/Server/movies/"
self.showPath = "/media/plex/Server/Shows/"
#Returns a list of no more than 5 options when user requests a movie
async def requestMovie(self, ctx, movieName):
await self.bot.defer(ctx)
self.bot.log("Searching for "+movieName)
movieList = imdb.IMDb().search_movie(movieName)
movies = []
for movie in movieList:
if movie["kind"] == "movie":
movies.append(movie)
if len(movies) > 5:
movies = movies[:5]
if len(movies) == 1:
messageTitle = "**Is it this movie?**"
else:
messageTitle = "**Is it any of these movies?**"
messageText = ""
imdbIds = []
for x, movie in enumerate(movies):
try:
messageText += "\n"+str(x+1)+") "+movie["title"]+" ("+str(movie["year"])+")"
except:
try:
messageText += "\n"+str(x+1)+") "+movie["title"]
except:
messageText += "Error"
imdbIds.append(movie.movieID)
self.bot.log("Returning a list of "+str(len(movies))+" possible movies: "+str(imdbIds))
em = discord.Embed(title=messageTitle,description=messageText,colour=0x00FF00)
message = await ctx.send(embed=em)
messageData = {"messageID":message.id,"imdbIds":imdbIds}
with open("resources/bedreNetflix/oldMessage"+str(ctx.channel.id),"w") as f:
json.dump(messageData,f)
if len(movies) == 1:
await message.add_reaction("✔️")
else:
for x in range(len(movies)):
await message.add_reaction(["1","2","3","4","5"][x])
await message.add_reaction("")
message = await ctx.channel.fetch_message(message.id)
if message.content != "" and not isinstance(ctx.channel, discord.DMChannel):
await message.clear_reactions()
#Adds the requested movie to Bedre Netflix
async def addMovie(self, message, imdbId, editMessage = True):
if imdbId == None:
self.bot.log("Did not find what the user was searching for")
if editMessage:
await message.edit(embed = None, content = "Try searching for the IMDB id")
else:
await message.channel.send("Try searching for the IMDB id")
else:
self.bot.log("Trying to add movie "+str(imdbId))
apiKey = self.bot.credentials.radarrKey
response = requests.get(self.radarrURL+"movie/lookup/imdb?imdbId=tt"+imdbId+"&apiKey="+apiKey)
lookupData = response.json()
postData = {"qualityProfileId": 1,
"rootFolderPath" : self.moviePath,
"monitored" : True,
"addOptions": {"searchForMovie": True}}
for key in ["tmdbId","title","titleSlug","images","year"]:
postData.update({key : lookupData[key]})
r = requests.post(url= self.radarrURL+"movie?apikey="+apiKey,json = postData)
if r.status_code == 201:
if editMessage:
await message.edit(embed = None, content = postData["title"]+" successfully added to Bedre Netflix")
else:
await message.channel.send(postData["title"]+" successfully added to Bedre Netflix")
self.bot.log("Added "+postData["title"]+" to Bedre Netflix")
elif r.status_code == 400:
text = f"{postData['title']} is either already on Bedre Netflix, downloading, or not available"
if editMessage:
await message.edit(embed = None, content = text)
else:
await message.channel.send(text)
else:
if editMessage:
await message.edit(embed = None, content = "Something went wrong")
else:
await message.channel.send("Something went wrong")
self.bot.log(str(r.status_code)+" "+r.reason)
#Returns a list of no more than 5 options when user requests a show
async def requestShow(self, ctx, showName):
await self.bot.defer(ctx)
self.bot.log("Searching for "+showName)
movies = imdb.IMDb().search_movie(showName) #Replace with tvdb
shows = []
for movie in movies:
if movie["kind"] in ["tv series","tv miniseries"]:
shows.append(movie)
if len(shows) > 5:
shows = shows[:5]
if len(shows) == 1:
messageTitle = "**Is it this show?**"
else:
messageTitle = "**Is it any of these shows?**"
messageText = ""
imdbNames = []
for x, show in enumerate(shows):
try:
messageText += "\n"+str(x+1)+") "+show["title"]+" ("+str(show["year"])+")"
except:
try:
messageText += "\n"+str(x+1)+") "+show["title"]
except:
messageText += "Error"
imdbNames.append(show["title"])
self.bot.log("Returning a list of "+str(len(shows))+" possible shows: "+str(imdbNames))
em = discord.Embed(title=messageTitle,description=messageText,colour=0x00FF00)
message = await ctx.send(embed=em)
messageData = {"messageID":message.id,"imdbNames":imdbNames}
with open("resources/bedreNetflix/oldMessage"+str(ctx.channel.id),"w") as f:
json.dump(messageData,f)
if len(shows) == 1:
await message.add_reaction("✔️")
else:
for x in range(len(shows)):
await message.add_reaction(["1","2","3","4","5"][x])
await message.add_reaction("")
message = await ctx.channel.fetch_message(message.id)
if message.content != "" and not isinstance(ctx.channel, discord.DMChannel):
await message.clear_reactions()
#Adds the requested show to Bedre Netflix
async def addShow(self, message, imdbName):
if imdbName == None:
self.bot.log("Did not find what the user was searching for")
await message.edit(embed = None, content = "Try searching for the IMDB id")
else:
self.bot.log("Trying to add show "+str(imdbName))
apiKey = self.bot.credentials.sonarrKey
response = requests.get(self.sonarrURL+"series/lookup?term="+imdbName.replace(" ","%20")+"&apiKey="+apiKey)
lookupData = response.json()[0]
postData = {"ProfileId" : 1,
"rootFolderPath" : self.showPath,
"monitored" : True,
"addOptions" : {"searchForMissingEpisodes" : True}}
for key in ["tvdbId","title","titleSlug","images","seasons"]:
postData.update({key : lookupData[key]})
r = requests.post(url= self.sonarrURL+"series?apikey="+apiKey,json = postData)
if r.status_code == 201:
await message.edit(embed = None, content = postData["title"]+" successfully added to Bedre Netflix")
self.bot.log("Added a "+postData["title"]+" to Bedre Netflix")
elif r.status_code == 400:
text = f"{postData['title']} is either already on Bedre Netflix, downloading, or not available"
await message.edit(embed = None, content = text)
else:
await message.edit(embed = None, content = "Something went wrong")
self.bot.log(str(r.status_code)+" "+r.reason)
#Generates a list of all torrents and returns formatted list and whether all torrents are downloaded
async def genDownloadList(self, showDM, showMovies, showShows, episodes):
self.bot.log("Generating torrent list")
titleWidth = 100
message = []
allDownloaded = True
if showDM:
message.append("")
DMSectionTitle = "*Torrent Downloads*"
DMSectionTitleLine = "-"*((titleWidth-len(DMSectionTitle))//2)
message.append(DMSectionTitleLine+DMSectionTitle+DMSectionTitleLine)
response = requests.get(self.qbittorrentURL+"torrents/info")
torrentList = response.json()
if len(torrentList) > 0:
for torrent in torrentList:
torrentName = torrent["name"]
if len(torrentName) > 30:
if torrentName[26] == " ":
torrentName = torrentName[:26]+"...."
else:
torrentName = torrentName[:27]+"..."
while len(torrentName) < 30:
torrentName += " "
if torrent["size"] == 0:
downloadedRatio = 0
elif torrent["amount_left"] == 0:
downloadedRatio = 1
else:
downloadedRatio = min(torrent["downloaded"]/torrent["size"],1)
progressBar = "|"+(""*math.floor(downloadedRatio*20))
while len(progressBar) < 21:
progressBar += " "
progressBar += "| "+str(math.floor(downloadedRatio*100))+"%"
while len(progressBar) < 27:
progressBar += " "
etaInSeconds = torrent["eta"]
if etaInSeconds >= 8640000:
eta = ""
else:
eta = ""
if etaInSeconds >= 86400:
eta += str(math.floor(etaInSeconds/86400))+"d "
if etaInSeconds >= 3600:
eta += str(math.floor((etaInSeconds%86400)/3600))+"h "
if etaInSeconds >= 60:
eta += str(math.floor((etaInSeconds%3600)/60))+"m "
eta += str(etaInSeconds%60)+"s"
torrentInfo = torrentName+" "+progressBar+" (Eta: "+eta+")"
if torrent["state"] == "stalledDL":
torrentInfo += " (Stalled)"
if not (downloadedRatio == 1 and torrent["last_activity"] < time.time()-7200):
message.append(torrentInfo)
if downloadedRatio < 1 and torrent["state"] != "stalledDL":
allDownloaded = False
else:
message.append("No torrents currently downloading")
if showMovies:
message.append("")
movieSectionTitle = "*Missing movies not downloading*"
movieSectionTitleLine = "-"*((titleWidth-len(movieSectionTitle))//2)
message.append(movieSectionTitleLine+movieSectionTitle+movieSectionTitleLine)
movieList = requests.get(self.radarrURL+"movie?apiKey="+self.bot.credentials.radarrKey).json()
movieQueue = requests.get(self.radarrURL+"queue?apiKey="+self.bot.credentials.radarrKey).json()
movieQueueIDs = []
for queueItem in movieQueue["records"]:
movieQueueIDs.append(queueItem["movieId"])
for movie in movieList:
if not movie["hasFile"]:
if movie["id"] not in movieQueueIDs:
movieName = movie["title"]
if len(movieName) > 40:
if movieName[36] == " ":
movieName = movieName[:36]+"...."
else:
movieName = movieName[:37]+"..."
while len(movieName) < 41:
movieName += " "
if movie["monitored"]:
movieInfo = movieName+"Could not find a torrent"
else:
movieInfo = movieName+"No torrent exists. Likely because the movie is not yet released on DVD"
message.append(movieInfo)
if showShows:
message.append("")
showSectionTitle = "*Missing shows not downloading*"
showSectionTitleLine = "-"*((titleWidth-len(showSectionTitle))//2)
message.append(showSectionTitleLine+showSectionTitle+showSectionTitleLine)
showList = requests.get(self.sonarrURL+"series?apiKey="+self.bot.credentials.sonarrKey).json()
for show in showList:
if show["seasons"][0]["seasonNumber"] == 0:
seasons = show["seasons"][1:]
else:
seasons = show["seasons"]
if any(i["statistics"]["episodeCount"] != i["statistics"]["totalEpisodeCount"] for i in seasons):
if all(i["statistics"]["episodeCount"] == 0 for i in seasons):
message.append(show["title"] + " (all episodes)")
else:
if episodes:
missingEpisodes = sum(i["statistics"]["totalEpisodeCount"] - i["statistics"]["episodeCount"] for i in seasons)
message.append(show["title"] + f" ({missingEpisodes} episodes)")
message.append("-"*titleWidth)
messageText = "```"+"\n".join(message[1:])+"```"
if messageText == "``````":
messageText = "There are no torrents downloading right. If the torrent you're looking for was added more than 24 hours ago, it might already be on Bedre Netflix."
return messageText, allDownloaded
async def downloading(self, ctx, content):
async def SendLongMessage(ctx,messageText):
if len(messageText) <= 1994:
await ctx.send("```"+messageText+"```")
else:
cutOffIndex = messageText[:1994].rfind("\n")
await ctx.send("```"+messageText[:cutOffIndex]+"```")
await SendLongMessage(ctx,messageText[cutOffIndex+1:])
await self.bot.defer(ctx)
# showDM, showMovies, showShows, episodes
params = [False, False, False, False]
showDMArgs = ["d", "dm", "downloading", "downloadmanager"]
showMoviesArgs = ["m", "movies"]
showShowsArgs = ["s", "shows", "series"]
episodesArgs = ["e", "episodes"]
argList = [showDMArgs, showMoviesArgs, showShowsArgs, episodesArgs]
inputArgs = []
validArguments = True
while content != "" and validArguments:
if content[0] == " ":
content = content[1:]
elif content[0] == "-":
if content[1] == "-":
argStart = 2
if " " in content:
argStop = content.find(" ")
else:
argStop = None
else:
argStart = 1
argStop = 2
inputArgs.append(content[argStart:argStop])
if argStop is None:
content = ""
else:
content = content[argStop:]
else:
validArguments = False
if validArguments:
for x, argAliases in enumerate(argList):
argInInput = [i in inputArgs for i in argAliases]
if any(argInInput):
inputArgs.remove(argAliases[argInInput.index(True)])
params[x] = True
if len(inputArgs) != 0 or (params[2] == False and params[3] == True):
validArguments = False
showAnything = any(i for i in params)
if validArguments and showAnything:
messageText, allDownloaded = await self.genDownloadList(*params)
if messageText.startswith("```"):
if len(messageText) <= 2000:
if not allDownloaded:
updatesLeft = 60
messageText = messageText[:-3]+"\nThis message will update every 10 seconds for "+str(math.ceil(updatesLeft/6))+" more minutes\n```"
oldMessage = await ctx.send(messageText)
while ((not allDownloaded) and updatesLeft > 0):
await asyncio.sleep(10)
updatesLeft -= 1
messageText, allDownloaded = await self.genDownloadList(*params)
messageText = messageText[:-3]+"\nThis message will update every 10 seconds for "+str(math.ceil(updatesLeft/6))+" more minutes\n```"
await oldMessage.edit(content = messageText)
messageText, allDownloaded = await self.genDownloadList(*params)
if messageText.startswith("```"):
if allDownloaded:
self.bot.log("All torrents are downloaded")
else:
messageText = messageText[:-3]+"\nThis message will not update anymore\n```"
self.bot.log("The message updated 20 times")
await oldMessage.edit(content = messageText)
else:
await ctx.send(messageText)
else:
messageText = messageText[3:-3]
await SendLongMessage(ctx,messageText)
else:
await ctx.send(messageText)
else:
await ctx.send("Invalid or repeated parameters. Use '/help downloading' to see valid parameters.")

6
gwendolyn/__init__.py Normal file
View File

@ -0,0 +1,6 @@
"""The main module for Gwendolyn."""
# pylint: disable=invalid-name
__all__ = ["funcs", "utils", "Gwendolyn"]
from .gwendolyn_client import Gwendolyn

View File

@ -0,0 +1,40 @@
"""Contains the EventCog, which runs code for specific bot events."""
from discord.ext import commands # Has the cog class
class EventCog(commands.Cog):
"""Handles bot events."""
def __init__(self, bot):
"""Initialize the bot."""
self.bot = bot
self.bot.on_error = self.on_error
@commands.Cog.listener()
async def on_ready(self):
"""Log and set bot status when bot logs in."""
await self.bot.event_handler.on_ready()
@commands.Cog.listener()
async def on_slash_command(self, ctx):
"""Log when a slash command is run."""
await self.bot.event_handler.on_slash_command(ctx)
@commands.Cog.listener()
async def on_slash_command_error(self, ctx, error):
"""Log when a slash error occurs."""
await self.bot.error_handler.on_slash_command_error(ctx, error)
async def on_error(self, method, *args, **kwargs): # pylint: disable=unused-argument
"""Log when an error occurs."""
await self.bot.error_handler.on_error(method)
@commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
"""Handle when someone reacts to a message."""
await self.bot.event_handler.on_reaction_add(reaction, user)
def setup(bot):
"""Add the eventcog to the bot."""
bot.add_cog(EventCog(bot))

177
gwendolyn/cogs/game_cog.py Normal file
View File

@ -0,0 +1,177 @@
"""Contains all the cogs that deal with game commands."""
from discord.ext import commands # Has the cog class
from discord_slash import cog_ext # Used for slash commands
from gwendolyn.utils import get_params # pylint: disable=import-error
params = get_params()
class GamesCog(commands.Cog):
"""Contains miscellaneous game commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
@cog_ext.cog_slash(**params["balance"])
async def balance(self, ctx):
"""Check user balance."""
await self.bot.money.sendBalance(ctx)
@cog_ext.cog_slash(**params["give"])
async def give(self, ctx, user, amount):
"""Give another user an amount of GwendoBucks."""
await self.bot.money.giveMoney(ctx, user, amount)
@cog_ext.cog_slash(**params["invest"])
async def invest(self, ctx, parameters="check"):
"""Invest GwendoBucks in the stock market."""
await self.bot.games.invest.parseInvest(ctx, parameters)
@cog_ext.cog_slash(**params["trivia"])
async def trivia(self, ctx, answer=""):
"""Run a game of trivia."""
await self.bot.games.trivia.triviaParse(ctx, answer)
class BlackjackCog(commands.Cog):
"""Contains the blackjack commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
@cog_ext.cog_subcommand(**params["blackjack_start"])
async def blackjack_start(self, ctx):
"""Start a game of blackjack."""
await self.bot.games.blackjack.start(ctx)
@cog_ext.cog_subcommand(**params["blackjack_bet"])
async def blackjack_bet(self, ctx, bet):
"""Enter the game of blackjack with a bet."""
await self.bot.games.blackjack.enter_game(ctx, bet)
@cog_ext.cog_subcommand(**params["blackjack_stand"])
async def blackjack_stand(self, ctx, hand=""):
"""Stand on your hand in blackjack."""
await self.bot.games.blackjack.stand(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjack_hit"])
async def blackjack_hit(self, ctx, hand=0):
"""Hit on your hand in blackjack."""
await self.bot.games.blackjack.hit(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjack_double"])
async def blackjack_double(self, ctx, hand=0):
"""Double in blackjack."""
await self.bot.games.blackjack.double(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjack_split"])
async def blackjack_split(self, ctx, hand=0):
"""Split your hand in blackjack."""
await self.bot.games.blackjack.split(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjack_hilo"])
async def blackjack_hilo(self, ctx):
"""Get the hilo value for the deck in blackjack."""
await self.bot.games.blackjack.hilo(ctx)
@cog_ext.cog_subcommand(**params["blackjack_shuffle"])
async def blackjack_shuffle(self, ctx):
"""Shuffle the blackjack game."""
await self.bot.games.blackjack.shuffle(ctx)
@cog_ext.cog_subcommand(**params["blackjack_cards"])
async def blackjack_cards(self, ctx):
"""Get the amount of cards left in the blackjack deck."""
await self.bot.games.blackjack.cards(ctx)
class ConnectFourCog(commands.Cog):
"""Contains all the connect four commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
@cog_ext.cog_subcommand(**params["connect_four_start_user"])
async def connect_four_start_user(self, ctx, user):
"""Start a game of connect four against another user."""
await self.bot.games.connect_four.start(ctx, user)
@cog_ext.cog_subcommand(**params["connect_four_start_gwendolyn"])
async def connect_four_start_gwendolyn(self, ctx, difficulty=3):
"""Start a game of connect four against Gwendolyn."""
await self.bot.games.connect_four.start(ctx, difficulty)
@cog_ext.cog_subcommand(**params["connect_four_surrender"])
async def connect_four_surrender(self, ctx):
"""Surrender the game of connect four."""
await self.bot.games.connect_four.surrender(ctx)
class HangmanCog(commands.Cog):
"""Contains all the hangman commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
@cog_ext.cog_subcommand(**params["hangman_start"])
async def hangman_start(self, ctx):
"""Start a game of hangman."""
await self.bot.games.hangman.start(ctx)
@cog_ext.cog_subcommand(**params["hangman_stop"])
async def hangman_stop(self, ctx):
"""Stop the current game of hangman."""
await self.bot.games.hangman.stop(ctx)
class HexCog(commands.Cog):
"""Contains all the hex commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
self.hex = self.bot.games.hex
@cog_ext.cog_subcommand(**params["hex_start_user"])
async def hex_start_user(self, ctx, user):
"""Start a game of hex against another player."""
await self.hex.start(ctx, user)
@cog_ext.cog_subcommand(**params["hex_start_gwendolyn"])
async def hex_start_gwendolyn(self, ctx, difficulty=2):
"""Start a game of hex against Gwendolyn."""
await self.hex.start(ctx, difficulty)
@cog_ext.cog_subcommand(**params["hex_place"])
async def hex_place(self, ctx, coordinates):
"""Place a piece in the hex game."""
await self.hex.placeHex(ctx, coordinates, f"#{ctx.author.id}")
@cog_ext.cog_subcommand(**params["hex_undo"])
async def hex_undo(self, ctx):
"""Undo your last hex move."""
await self.hex.undo(ctx)
@cog_ext.cog_subcommand(**params["hex_swap"])
async def hex_swap(self, ctx):
"""Perform a hex swap."""
await self.hex.swap(ctx)
@cog_ext.cog_subcommand(**params["hex_surrender"])
async def hex_surrender(self, ctx):
"""Surrender the hex game."""
await self.hex.surrender(ctx)
def setup(bot):
"""Add all the cogs to the bot."""
bot.add_cog(GamesCog(bot))
bot.add_cog(BlackjackCog(bot))
bot.add_cog(ConnectFourCog(bot))
bot.add_cog(HangmanCog(bot))
bot.add_cog(HexCog(bot))

View File

@ -0,0 +1,32 @@
"""Contains the LookupCog, which deals with the lookup commands."""
from discord.ext import commands # Has the cog class
from discord_slash import cog_ext # Used for slash commands
from gwendolyn.utils import get_params # pylint: disable=import-error
params = get_params()
class LookupCog(commands.Cog):
"""Contains the lookup commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
# Looks up a spell
@cog_ext.cog_slash(**params["spell"])
async def spell(self, ctx, query):
"""Look up a spell."""
await self.bot.lookup_funcs.spellFunc(ctx, query)
# Looks up a monster
@cog_ext.cog_slash(**params["monster"])
async def monster(self, ctx, query):
"""Look up a monster."""
await self.bot.lookup_funcs.monsterFunc(ctx, query)
def setup(bot):
"""Add the cog to the bot."""
bot.add_cog(LookupCog(bot))

View File

@ -0,0 +1,99 @@
"""Contains the MiscCog, which deals with miscellaneous commands."""
from discord.ext import commands # Has the cog class
from discord_slash import cog_ext # Used for slash commands
from gwendolyn.utils import get_params # pylint: disable=import-error
params = get_params()
class MiscCog(commands.Cog):
"""Contains the miscellaneous commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
self.bot.remove_command("help")
self.generators = bot.other.generators
self.plex = bot.other.plex
self.nerd_shit = bot.other.nerd_shit
@cog_ext.cog_slash(**params["ping"])
async def ping(self, ctx):
"""Send the bot's latency."""
await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms")
@cog_ext.cog_slash(**params["stop"])
async def stop(self, ctx):
"""Stop the bot."""
await self.bot.stop(ctx)
@cog_ext.cog_slash(**params["help"])
async def help_command(self, ctx, command=""):
"""Get help for commands."""
await self.bot.other.helpFunc(ctx, command)
@cog_ext.cog_slash(**params["thank"])
async def thank(self, ctx):
"""Thank the bot."""
await ctx.send("You're welcome :blush:")
@cog_ext.cog_slash(**params["hello"])
async def hello(self, ctx):
"""Greet the bot."""
await self.bot.other.helloFunc(ctx)
@cog_ext.cog_slash(**params["roll"])
async def roll(self, ctx, dice="1d20"):
"""Roll dice."""
await self.bot.other.rollDice(ctx, dice)
@cog_ext.cog_slash(**params["image"])
async def image(self, ctx):
"""Get a random image from Bing."""
await self.bot.other.imageFunc(ctx)
@cog_ext.cog_slash(**params["movie"])
async def movie(self, ctx):
"""Get a random movie from the Plex server."""
await self.bot.other.movieFunc(ctx)
@cog_ext.cog_slash(**params["name"])
async def name(self, ctx):
"""Generate a random name."""
await self.generators.nameGen(ctx)
@cog_ext.cog_slash(**params["tavern"])
async def tavern(self, ctx):
"""Generate a random tavern name."""
await self.generators.tavernGen(ctx)
@cog_ext.cog_slash(**params["wiki"])
async def wiki(self, ctx, wiki_page=""):
"""Get a page on a fandom wiki."""
await self.bot.other.findWikiPage(ctx, wiki_page)
@cog_ext.cog_slash(**params["add_movie"])
async def add_movie(self, ctx, movie):
"""Search for a movie and add it to the Plex server."""
await self.plex.request_movie(ctx, movie)
@cog_ext.cog_slash(**params["add_show"])
async def add_show(self, ctx, show):
"""Search for a show and add it to the Plex server."""
await self.plex.request_show(ctx, show)
@cog_ext.cog_slash(**params["downloading"])
async def downloading(self, ctx, parameters="-d"):
"""Get the current downloading torrents."""
await self.plex.downloading(ctx, parameters)
@cog_ext.cog_slash(**params["wolf"])
async def wolf(self, ctx, query):
"""Perform a search on Wolfram Alpha."""
await self.nerd_shit.wolfSearch(ctx, query)
def setup(bot):
"""Add the cog to the bot."""
bot.add_cog(MiscCog(bot))

View File

@ -0,0 +1,40 @@
"""Contains the StarWarsCog, which deals with Star Wars commands."""
from discord.ext import commands
from discord_slash import cog_ext
from gwendolyn.utils import get_params # pylint: disable=import-error
params = get_params()
class StarWarsCog(commands.Cog):
"""Contains the Star Wars commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
@cog_ext.cog_slash(**params["star_wars_roll"])
async def star_wars_roll(self, ctx, dice=""):
"""Roll Star Wars dice."""
await self.bot.star_wars.roll.parseRoll(ctx, dice)
@cog_ext.cog_slash(**params["star_wars_destiny"])
async def star_wars_destiny(self, ctx, parameters=""):
"""Control Star Wars destiny points."""
await self.bot.star_wars.destiny.parseDestiny(ctx, parameters)
@cog_ext.cog_slash(**params["star_wars_crit"])
async def star_wars_crit(self, ctx, severity: int = 0):
"""Roll for critical injuries."""
await self.bot.star_wars.roll.critRoll(ctx, severity)
@cog_ext.cog_slash(**params["star_wars_character"])
async def star_wars_character(self, ctx, parameters=""):
"""Access and change Star Wars character sheet data."""
await self.bot.star_wars.character.parseChar(ctx, parameters)
def setup(bot):
"""Add the cog to the bot."""
bot.add_cog(StarWarsCog(bot))

View File

@ -1,6 +1,6 @@
"""A collection of all Gwendolyn functions."""
__all__ = ["Games" , "Money", "LookupFuncs", "StarWars"]
__all__ = ["Games", "Money", "LookupFuncs", "StarWars"]
from .games import Money, Games
@ -8,4 +8,4 @@ from .lookup import LookupFuncs
from .other import Other
from .starWarsFuncs import StarWars
from .star_wars_funcs import StarWars

View File

@ -3,4 +3,4 @@
__all__ = ["Money", "Games"]
from .money import Money
from .gamesContainer import Games
from .games_container import Games

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
"""
Has a container for game functions.
*Classes*
---------
Games
Container for game functions.
"""
from .invest import Invest
from .trivia import Trivia
from .blackjack import Blackjack
from .connect_four import ConnectFour
from .hangman import Hangman
from .hex import HexGame
class Games():
"""
Contains game classes.
*Attributes*
------------
bot: Gwendolyn
The instance of Gwendolyn.
invest
Contains investment functions.
blackjack
Contains blackjack functions.
connect_four
Contains connect four functions.
hangman
Contains hangman functions.
hex
Contains hex functions
"""
def __init__(self, bot):
"""Initialize the container."""
self.bot = bot
self.invest = Invest(bot)
self.trivia = Trivia(bot)
self.blackjack = Blackjack(bot)
self.connect_four = ConnectFour(bot)
self.hangman = Hangman(bot)
self.hex = HexGame(bot)

View File

@ -0,0 +1,627 @@
"""
Deals with commands and logic for hangman games.
*Classes*
---------
Hangman()
Deals with the game logic of hangman.
DrawHangman()
Draws the image shown to the player.
"""
import datetime # Used for generating the game id
import string # string.ascii_uppercase used
import math # Used by DrawHangman(), mainly for drawing circles
import random # Used to draw poorly
import requests # Used for getting the word in Hangman.start()
import discord # Used for discord.file and type hints
from discord_slash.context import SlashContext # Used for typehints
from PIL import ImageDraw, Image, ImageFont # Used to draw the image
class Hangman():
"""
Controls hangman commands and game logic.
*Methods*
---------
start(ctx: SlashContext)
stop(ctx: SlashContext)
guess(message: discord.message, user: str, guess: str)
"""
def __init__(self, bot):
"""
Initialize the class.
*Attributes*
------------
draw: DrawHangman
The DrawHangman used to draw the hangman image.
API_url: str
The url to get the words from.
APIPARAMS: dict
The parameters to pass to every api call.
"""
self.__bot = bot
self.__draw = DrawHangman(bot)
self.__API_url = "https://api.wordnik.com/v4/words.json/randomWords?" # pylint: disable=invalid-name
api_key = self.__bot.credentials["wordnik_key"]
self.__APIPARAMS = { # pylint: disable=invalid-name
"hasDictionaryDef": True,
"minCorpusCount": 5000,
"maxCorpusCount": -1,
"minDictionaryCount": 1,
"maxDictionaryCount": -1,
"minLength": 3,
"maxLength": 11,
"limit": 1,
"api_key": api_key
}
async def start(self, ctx: SlashContext):
"""
Start a game of hangman.
*Parameters*
------------
ctx: SlashContext
The context of the command.
"""
await self.__bot.defer(ctx)
channel = str(ctx.channel_id)
user = f"#{ctx.author.id}"
game = self.__bot.database["hangman games"].find_one({"_id": channel})
user_name = self.__bot.database_funcs.get_name(user)
started_game = False
if game is None:
word = "-"
while "-" in word or "." in word:
response = requests.get(self.__API_url, params=self.__APIPARAMS)
word = list(response.json()[0]["word"].upper())
self.__bot.log("Found the word \""+"".join(word)+"\"")
guessed = [False] * len(word)
game_id = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
new_game = {
"_id": channel,
"player": user,
"guessed letters": [],
"word": word,
"game ID": game_id,
"misses": 0,
"guessed": guessed
}
self.__bot.database["hangman games"].insert_one(new_game)
remaining_letters = list(string.ascii_uppercase)
self.__draw.draw_image(channel)
log_message = "Game started"
send_message = f"{user_name} started game of hangman."
started_game = True
else:
log_message = "There was already a game going on"
send_message = self.__bot.long_strings["Hangman going on"]
self.__bot.log(log_message)
await ctx.send(send_message)
if started_game:
boards_path = "gwendolyn/resources/games/hangman_boards/"
file_path = f"{boards_path}hangman_board{channel}.png"
new_image = await ctx.channel.send(file=discord.File(file_path))
blank_message = await ctx.channel.send("_ _")
reaction_messages = {
new_image: remaining_letters[:15],
blank_message: remaining_letters[15:]
}
old_messages = f"{new_image.id}\n{blank_message.id}"
old_images_path = "gwendolyn/resources/games/old_images/"
with open(old_images_path+f"hangman{channel}", "w") as file_pointer:
file_pointer.write(old_messages)
for message, letters in reaction_messages.items():
for letter in letters:
emoji = chr(ord(letter)+127397)
await message.add_reaction(emoji)
async def stop(self, ctx: SlashContext):
"""
Stop the game of hangman.
*Parameters*
------------
ctx: SlashContext
The context of the command.
"""
channel = str(ctx.channel.id)
game = self.__bot.database["hangman games"].find_one({"_id": channel})
if game is None:
await ctx.send("There's no game going on")
elif f"#{ctx.author.id}" != game["player"]:
await ctx.send("You can't end a game you're not in")
else:
self.__bot.database["hangman games"].delete_one({"_id": channel})
old_images_path = "gwendolyn/resources/games/old_images/"
with open(old_images_path+f"hangman{channel}", "r") as file_pointer:
messages = file_pointer.read().splitlines()
for message in messages:
old_message = await ctx.channel.fetch_message(int(message))
self.__bot.log("Deleting old message")
await old_message.delete()
await ctx.send("Game stopped")
async def guess(self, message: discord.Message, user: str, guess: str):
"""
Guess a letter.
*Parameters*
------------
message: discord.Message
The message that the reaction was placed on.
user: str
The id of the user.
guess: str
The guess.
"""
channel = str(message.channel.id)
hangman_games = self.__bot.database["hangman games"]
game = hangman_games.find_one({"_id": channel})
game_exists = (game is not None)
single_letter = (len(guess) == 1 and guess.isalpha())
new_guess = (guess not in game["guessed letters"])
valid_guess = (game_exists and single_letter and new_guess)
if valid_guess:
self.__bot.log("Guessed the letter")
correct_guess = 0
for i, letter in enumerate(game["word"]):
if guess == letter:
correct_guess += 1
updater = {"$set": {f"guessed.{i}": True}}
hangman_games.update_one({"_id": channel}, updater)
if correct_guess == 0:
updater = {"$inc": {"misses": 1}}
hangman_games.update_one({"_id": channel}, updater)
updater = {"$push": {"guessed letters": guess}}
hangman_games.update_one({"_id": channel}, updater)
remaining_letters = list(string.ascii_uppercase)
game = hangman_games.find_one({"_id": channel})
for letter in game["guessed letters"]:
remaining_letters.remove(letter)
if correct_guess == 1:
send_message = "Guessed {}. There was 1 {} in the word."
send_message = send_message.format(guess, guess)
else:
send_message = "Guessed {}. There were {} {}s in the word."
send_message = send_message.format(guess, correct_guess, guess)
self.__draw.draw_image(channel)
if game["misses"] == 6:
hangman_games.delete_one({"_id": channel})
send_message += self.__bot.long_strings["Hangman lost game"]
remaining_letters = []
elif all(game["guessed"]):
hangman_games.delete_one({"_id": channel})
self.__bot.money.addMoney(user, 15)
send_message += self.__bot.long_strings["Hangman guessed word"]
remaining_letters = []
await message.channel.send(send_message)
old_images_path = "gwendolyn/resources/games/old_images/"
with open(old_images_path+f"hangman{channel}", "r") as file_pointer:
old_message_ids = file_pointer.read().splitlines()
for old_id in old_message_ids:
old_message = await message.channel.fetch_message(int(old_id))
self.__bot.log("Deleting old message")
await old_message.delete()
boards_path = "gwendolyn/resources/games/hangman_boards/"
file_path = f"{boards_path}hangman_board{channel}.png"
new_image = await message.channel.send(file=discord.File(file_path))
if len(remaining_letters) > 0:
if len(remaining_letters) > 15:
blank_message = await message.channel.send("_ _")
reaction_messages = {
new_image: remaining_letters[:15],
blank_message: remaining_letters[15:]
}
else:
blank_message = ""
reaction_messages = {new_image: remaining_letters}
if blank_message != "":
old_messages = f"{new_image.id}\n{blank_message.id}"
else:
old_messages = str(new_image.id)
old_images_path = "gwendolyn/resources/games/old_images/"
old_image_path = old_images_path + f"hangman{channel}"
with open(old_image_path, "w") as file_pointer:
file_pointer.write(old_messages)
for message, letters in reaction_messages.items():
for letter in letters:
emoji = chr(ord(letter)+127397)
await message.add_reaction(emoji)
class DrawHangman():
"""
Draws the image of the hangman game.
*Methods*
---------
draw_image(channel: str)
"""
def __init__(self, bot):
"""
Initialize the class.
*Attributes*
------------
CIRCLESIZE
LINEWIDTH
BODYSIZE
LIMBSIZE
ARMPOSITION
MANX, MANY
LETTERLINELENGTH
LETTERLINEDISTANCE
GALLOWX, GALLOWY
PHI
FONT
SMALLFONT
"""
self.__bot = bot
# pylint: disable=invalid-name
self.__CIRCLESIZE = 120
self.__LINEWIDTH = 12
self.__BODYSIZE = 210
self.__LIMBSIZE = 60
self.__ARMPOSITION = 60
self.__MANX = (self.__LIMBSIZE*2)
self.__MANY = (self.__CIRCLESIZE+self.__BODYSIZE+self.__LIMBSIZE)
MANPADDING = self.__LINEWIDTH*4
self.__MANX += MANPADDING
self.__MANY += MANPADDING
self.__LETTERLINELENGTH = 90
self.__LETTERLINEDISTANCE = 30
self.__GALLOWX, self.__GALLOWY = 360, 600
self.__PHI = 1-(1 / ((1 + 5 ** 0.5) / 2))
LETTERSIZE = 75 # Wrong guesses letter size
WORDSIZE = 70 # Correct guesses letter size
FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf"
self.__FONT = ImageFont.truetype(FONTPATH, LETTERSIZE)
self.__SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE)
# pylint: enable=invalid-name
def __deviate(self, pre_deviance: int, pre_deviance_accuracy: int,
position_change: float, maxmin: int,
max_acceleration: float):
random_deviance = random.uniform(-position_change, position_change)
deviance_accuracy = pre_deviance_accuracy + random_deviance
if deviance_accuracy > maxmin * max_acceleration:
deviance_accuracy = maxmin * max_acceleration
elif deviance_accuracy < -maxmin * max_acceleration:
deviance_accuracy = -maxmin * max_acceleration
deviance = pre_deviance + deviance_accuracy
if deviance > maxmin:
deviance = maxmin
elif deviance < -maxmin:
deviance = -maxmin
return deviance, deviance_accuracy
def __bad_circle(self):
circle_padding = (self.__LINEWIDTH*3)
image_width = self.__CIRCLESIZE+circle_padding
image_size = (image_width, image_width)
background = Image.new("RGBA", image_size, color=(0, 0, 0, 0))
drawer = ImageDraw.Draw(background, "RGBA")
middle = (self.__CIRCLESIZE+(self.__LINEWIDTH*3))/2
deviance_x = 0
deviance_y = 0
deviance_accuracy_x = 0
deviance_accuracy_y = 0
start = random.randint(-100, -80)
degreess_amount = 360 + random.randint(-10, 30)
for degree in range(degreess_amount):
deviance_x, deviance_accuracy_x = self.__deviate(
deviance_x,
deviance_accuracy_x,
self.__LINEWIDTH/100,
self.__LINEWIDTH,
0.03
)
deviance_y, deviance_accuracy_y = self.__deviate(
deviance_y,
deviance_accuracy_y,
self.__LINEWIDTH/100,
self.__LINEWIDTH,
0.03
)
radians = math.radians(degree+start)
circle_x = (math.cos(radians) * (self.__CIRCLESIZE/2))
circle_y = (math.sin(radians) * (self.__CIRCLESIZE/2))
position_x = middle + circle_x - (self.__LINEWIDTH/2) + deviance_x
position_y = middle + circle_y - (self.__LINEWIDTH/2) + deviance_y
circle_position = [
(position_x, position_y),
(position_x+self.__LINEWIDTH, position_y+self.__LINEWIDTH)
]
drawer.ellipse(circle_position, fill=(0, 0, 0, 255))
return background
def __bad_line(self, length: int, rotated: bool = False):
if rotated:
width, height = length+self.__LINEWIDTH*3, self.__LINEWIDTH*3
else:
width, height = self.__LINEWIDTH*3, length+self.__LINEWIDTH*3
background = Image.new("RGBA", (width, height), color=(0, 0, 0, 0))
drawer = ImageDraw.Draw(background, "RGBA")
possible_deviance = int(self.__LINEWIDTH/3)
deviance_x = random.randint(-possible_deviance, possible_deviance)
deviance_y = 0
deviance_accuracy_x = 0
deviance_accuracy_y = 0
for pixel in range(length):
deviance_x, deviance_accuracy_x = self.__deviate(
deviance_x,
deviance_accuracy_x,
self.__LINEWIDTH/1000,
self.__LINEWIDTH,
0.004
)
deviance_y, deviance_accuracy_y = self.__deviate(
deviance_y,
deviance_accuracy_y,
self.__LINEWIDTH/1000,
self.__LINEWIDTH,
0.004
)
if rotated:
position_x = self.__LINEWIDTH + pixel + deviance_x
position_y = self.__LINEWIDTH + deviance_y
else:
position_x = self.__LINEWIDTH + deviance_x
position_y = self.__LINEWIDTH + pixel + deviance_y
circle_position = [
(position_x, position_y),
(position_x+self.__LINEWIDTH, position_y+self.__LINEWIDTH)
]
drawer.ellipse(circle_position, fill=(0, 0, 0, 255))
return background
def __draw_man(self, misses: int, seed: str):
random.seed(seed)
man_size = (self.__MANX, self.__MANY)
background = Image.new("RGBA", man_size, color=(0, 0, 0, 0))
if misses >= 1:
head = self.__bad_circle()
paste_x = (self.__MANX-(self.__CIRCLESIZE+(self.__LINEWIDTH*3)))//2
paste_position = (paste_x, 0)
background.paste(head, paste_position, head)
if misses >= 2:
body = self.__bad_line(self.__BODYSIZE)
paste_x = (self.__MANX-(self.__LINEWIDTH*3))//2
paste_position = (paste_x, self.__CIRCLESIZE)
background.paste(body, paste_position, body)
if misses >= 3:
limbs = random.sample(["rl", "ll", "ra", "la"], min(misses-2, 4))
else:
limbs = []
random.seed(seed)
for limb in limbs:
limb_drawing = self.__bad_line(self.__LIMBSIZE, True)
x_position = (self.__MANX-(self.__LINEWIDTH*3))//2
if limb[1] == "a":
rotation = random.randint(-45, 45)
shift = math.sin(math.radians(rotation))
line_length = self.__LIMBSIZE+(self.__LINEWIDTH*3)
compensation = int(shift*line_length)
limb_drawing = limb_drawing.rotate(rotation, expand=1)
y_position = self.__CIRCLESIZE + self.__ARMPOSITION
if limb == "ra":
compensation = min(-compensation, 0)
else:
x_position -= self.__LIMBSIZE
compensation = min(compensation, 0)
y_position += compensation
else:
rotation = random.randint(-15, 15)
y_position = self.__CIRCLESIZE+self.__BODYSIZE-self.__LINEWIDTH
if limb == "rl":
limb_drawing = limb_drawing.rotate(rotation-45, expand=1)
else:
x_position += -limb_drawing.size[0]+self.__LINEWIDTH*3
limb_drawing = limb_drawing.rotate(rotation+45, expand=1)
paste_position = (x_position, y_position)
background.paste(limb_drawing, paste_position, limb_drawing)
return background
def __bad_text(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)):
if big:
font = self.__FONT
else:
font = self.__SMALLFONT
width, height = font.getsize(text)
img = Image.new("RGBA", (width, height), color=(0, 0, 0, 0))
drawer = ImageDraw.Draw(img, "RGBA")
drawer.text((0, 0), text, font=font, fill=color)
return img
def __draw_gallows(self):
gallow_size = (self.__GALLOWX, self.__GALLOWY)
background = Image.new("RGBA", gallow_size, color=(0, 0, 0, 0))
bottom_line = self.__bad_line(int(self.__GALLOWX * 0.75), True)
bottom_line_x = int(self.__GALLOWX * 0.125)
bottom_line_y = self.__GALLOWY-(self.__LINEWIDTH*4)
paste_position = (bottom_line_x, bottom_line_y)
background.paste(bottom_line, paste_position, bottom_line)
line_two = self.__bad_line(self.__GALLOWY-self.__LINEWIDTH*6)
line_two_x = int(self.__GALLOWX*(0.75*self.__PHI))
line_two_y = self.__LINEWIDTH*2
paste_position = (line_two_x, line_two_y)
background.paste(line_two, paste_position, line_two)
top_line = self.__bad_line(int(self.__GALLOWY*0.30), True)
paste_x = int(self.__GALLOWX*(0.75*self.__PHI))-self.__LINEWIDTH
paste_position = (paste_x, self.__LINEWIDTH*3)
background.paste(top_line, paste_position, top_line)
last_line = self.__bad_line(int(self.__GALLOWY*0.125))
paste_x += int(self.__GALLOWY*0.30)
background.paste(last_line, (paste_x, self.__LINEWIDTH*3), last_line)
return background
def __draw_letter_lines(self, word: str, guessed: list, misses: int):
letter_width = self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE
image_width = letter_width*len(word)
image_size = (image_width, self.__LETTERLINELENGTH+self.__LINEWIDTH*3)
letter_lines = Image.new("RGBA", image_size, color=(0, 0, 0, 0))
for i, letter in enumerate(word):
line = self.__bad_line(self.__LETTERLINELENGTH, True)
paste_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE)
paste_position = (paste_x, self.__LETTERLINELENGTH)
letter_lines.paste(line, paste_position, line)
if guessed[i]:
letter_drawing = self.__bad_text(letter, True)
letter_width = self.__FONT.getsize(letter)[0]
letter_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE)
letter_x -= (letter_width//2)
letter_x += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2)
letter_lines.paste(
letter_drawing,
(letter_x, 0),
letter_drawing
)
elif misses == 6:
letter_drawing = self.__bad_text(letter, True, (242, 66, 54))
letter_width = self.__FONT.getsize(letter)[0]
letter_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE)
letter_x -= (letter_width//2)
letter_x += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2)
letter_lines.paste(
letter_drawing,
(letter_x, 0),
letter_drawing
)
return letter_lines
def __shortest_dist(self, positions: list, new_position: tuple):
shortest_dist = math.inf
for i, j in positions:
x_distance = abs(i-new_position[0])
y_distance = abs(j-new_position[1])
dist = math.sqrt(x_distance**2+y_distance**2)
if shortest_dist > dist:
shortest_dist = dist
return shortest_dist
def __draw_misses(self, guesses: list, word: str):
background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0))
pos = []
for guess in guesses:
if guess not in word:
placed = False
while not placed:
letter = self.__bad_text(guess, True)
w, h = self.__FONT.getsize(guess)
x = random.randint(0, 600-w)
y = random.randint(0, 400-h)
if self.__shortest_dist(pos, (x, y)) > 70:
pos.append((x, y))
background.paste(letter, (x, y), letter)
placed = True
return background
def draw_image(self, channel: str):
"""
Draw a hangman Image.
*Parameters*
------------
channel: str
The id of the channel the game is in.
"""
self.__bot.log("Drawing hangman image", channel)
game = self.__bot.database["hangman games"].find_one({"_id": channel})
random.seed(game["game ID"])
background = Image.open("gwendolyn/resources/paper.jpg")
gallow = self.__draw_gallows()
man = self.__draw_man(game["misses"], game["game ID"])
random.seed(game["game ID"])
letter_line_parameters = [game["word"], game["guessed"], game["misses"]]
letter_lines = self.__draw_letter_lines(*letter_line_parameters)
random.seed(game["game ID"])
misses = self.__draw_misses(game["guessed letters"], game["word"])
background.paste(gallow, (100, 100), gallow)
background.paste(man, (300, 210), man)
background.paste(letter_lines, (120, 840), letter_lines)
background.paste(misses, (600, 150), misses)
misses_text = self.__bad_text("MISSES", False)
misses_text_width = misses_text.size[0]
background.paste(misses_text, (850-misses_text_width//2, 50), misses_text)
board_path = f"gwendolyn/resources/games/hangman_boards/hangman_board{channel}.png"
background.save(board_path)

View File

@ -28,24 +28,24 @@ class HexGame():
await ctx.send("You can't surrender when you're not a player.")
else:
opponent = (players.index(user) + 1) % 2
opponentName = self.bot.databaseFuncs.getName(players[opponent])
opponent_name = self.bot.database_funcs.get_name(players[opponent])
self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":opponent + 1}})
await ctx.send(f"{ctx.author.display_name} surrendered")
with open(f"resources/games/oldImages/hex{channel}", "r") as f:
oldImage = await ctx.channel.fetch_message(int(f.read()))
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f:
old_image = await ctx.channel.fetch_message(int(f.read()))
if oldImage is not None:
await oldImage.delete()
if old_image is not None:
await old_image.delete()
else:
self.bot.log("The old image was already deleted")
self.bot.log("Sending the image")
filePath = f"resources/games/hexBoards/board{channel}.png"
oldImage = await ctx.channel.send(file = discord.File(filePath))
file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png"
old_image = await ctx.channel.send(file = discord.File(file_path))
with open(f"resources/games/oldImages/hex{channel}", "w") as f:
f.write(str(oldImage.id))
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f:
f.write(str(old_image.id))
self.bot.database["hex games"].delete_one({"_id":channel})
@ -71,26 +71,26 @@ class HexGame():
self.draw.drawSwap(channel)
opponent = game["players"][::-1][game["turn"]-1]
gwendoTurn = (opponent == f"#{self.bot.user.id}")
opponentName = self.bot.databaseFuncs.getName(opponent)
await ctx.send(f"The color of the players were swapped. It is now {opponentName}'s turn")
gwendolyn_turn = (opponent == f"#{self.bot.user.id}")
opponent_name = self.bot.database_funcs.get_name(opponent)
await ctx.send(f"The color of the players were swapped. It is now {opponent_name}'s turn")
with open(f"resources/games/oldImages/hex{channel}", "r") as f:
oldImage = await ctx.channel.fetch_message(int(f.read()))
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f:
old_image = await ctx.channel.fetch_message(int(f.read()))
if oldImage is not None:
await oldImage.delete()
if old_image is not None:
await old_image.delete()
else:
self.bot.log("The old image was already deleted")
self.bot.log("Sending the image")
filePath = f"resources/games/hexBoards/board{channel}.png"
oldImage = await ctx.channel.send(file = discord.File(filePath))
file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png"
old_image = await ctx.channel.send(file = discord.File(file_path))
with open(f"resources/games/oldImages/hex{channel}", "w") as f:
f.write(str(oldImage.id))
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f:
f.write(str(old_image.id))
if gwendoTurn:
if gwendolyn_turn:
await self.hexAI(ctx)
# Starts the game
@ -100,109 +100,109 @@ class HexGame():
channel = str(ctx.channel_id)
game = self.bot.database["hex games"].find_one({"_id":channel})
startedGame = False
canStart = True
started_game = False
can_start = True
if game != None:
sendMessage = "There's already a hex game going on in this channel"
logMessage = "There was already a game going on"
canStart = False
send_message = "There's already a hex game going on in this channel"
log_message = "There was already a game going on"
can_start = False
else:
if type(opponent) == int:
# Opponent is Gwendolyn
if opponent in range(1, 6):
opponentName = "Gwendolyn"
opponent_name = "Gwendolyn"
difficulty = int(opponent)
diffText = f" with difficulty {difficulty}"
difficulty_text = f" with difficulty {difficulty}"
opponent = f"#{self.bot.user.id}"
else:
sendMessage = "Difficulty doesn't exist"
logMessage = "They tried to play against a difficulty that doesn't exist"
canStart = False
send_message = "Difficulty doesn't exist"
log_message = "They tried to play against a difficulty that doesn't exist"
can_start = False
elif type(opponent) == discord.member.Member:
if opponent.bot:
# User has challenged a bot
if opponent == self.bot.user:
# It was Gwendolyn
opponentName = "Gwendolyn"
opponent_name = "Gwendolyn"
difficulty = 2
diffText = f" with difficulty {difficulty}"
difficulty_text = f" with difficulty {difficulty}"
opponent = f"#{self.bot.user.id}"
else:
sendMessage = "You can't challenge a bot!"
logMessage = "They tried to challenge a bot"
canStart = False
send_message = "You can't challenge a bot!"
log_message = "They tried to challenge a bot"
can_start = False
else:
# Opponent is another player
if ctx.author != opponent:
opponentName = opponent.display_name
opponent_name = opponent.display_name
opponent = f"#{opponent.id}"
difficulty = 5
diffText = ""
difficulty_text = ""
else:
sendMessage = "You can't play against yourself"
logMessage = "They tried to play against themself"
canStart = False
send_message = "You can't play against yourself"
log_message = "They tried to play against themself"
can_start = False
else:
canStart = False
logMessage = f"Opponent was neither int or member. It was {type(opponent)}"
sendMessage = "Something went wrong"
can_start = False
log_message = f"Opponent was neither int or member. It was {type(opponent)}"
send_message = "Something went wrong"
if canStart:
if can_start:
# board is 11x11
board = [[0 for i in range(self.BOARDWIDTH)] for j in range(self.BOARDWIDTH)]
players = [user, opponent]
random.shuffle(players) # random starting player
gameHistory = []
newGame = {"_id":channel,"board":board, "winner":0,
new_game = {"_id":channel,"board":board, "winner":0,
"players":players, "turn":1, "difficulty":difficulty, "gameHistory":gameHistory}
self.bot.database["hex games"].insert_one(newGame)
self.bot.database["hex games"].insert_one(new_game)
# draw the board
self.draw.drawBoard(channel)
gwendoTurn = (players[0] == f"#{self.bot.user.id}")
startedGame = True
gwendolyn_turn = (players[0] == f"#{self.bot.user.id}")
started_game = True
turnName = self.bot.databaseFuncs.getName(players[0])
sendMessage = f"Started Hex game against {opponentName}{diffText}. It's {turnName}'s turn"
logMessage = "Game started"
turn_name = self.bot.database_funcs.get_name(players[0])
send_message = f"Started Hex game against {opponent_name}{difficulty_text}. It's {turn_name}'s turn"
log_message = "Game started"
await ctx.send(sendMessage)
self.bot.log(logMessage)
await ctx.send(send_message)
self.bot.log(log_message)
if startedGame:
filePath = f"resources/games/hexBoards/board{ctx.channel_id}.png"
newImage = await ctx.channel.send(file = discord.File(filePath))
if started_game:
file_path = f"gwendolyn/resources/games/hex_boards/board{ctx.channel_id}.png"
new_image = await ctx.channel.send(file = discord.File(file_path))
with open(f"resources/games/oldImages/hex{ctx.channel_id}", "w") as f:
f.write(str(newImage.id))
with open(f"gwendolyn/resources/games/old_images/hex{ctx.channel_id}", "w") as f:
f.write(str(new_image.id))
if gwendoTurn:
if gwendolyn_turn:
await self.hexAI(ctx)
# Places a piece at the given location and checks things afterwards
async def placeHex(self, ctx, position : str, user):
channel = str(ctx.channel_id)
game = self.bot.database["hex games"].find_one({"_id":channel})
placedPiece = False
placed_piece = False
if game == None:
sendMessage = "There's no game in this channel"
send_message = "There's no game in this channel"
self.bot.log("There was no game going on")
elif not (position[0].isalpha() and position[1:].isnumeric() and len(position) in [2, 3]):
sendMessage = "The position must be a letter followed by a number."
send_message = "The position must be a letter followed by a number."
self.bot.log(f"The position was not valid, {position}")
else:
players = game["players"]
if user not in players:
sendMessage = f"You can't place when you're not in the game. The game's players are: {self.bot.databaseFuncs.getName(game['players'][0])} and {self.bot.databaseFuncs.getName(game['players'][1])}."
send_message = f"You can't place when you're not in the game. The game's players are: {self.bot.database_funcs.get_name(game['players'][0])} and {self.bot.database_funcs.get_name(game['players'][1])}."
self.bot.log("They aren't in the game")
elif players[game["turn"]-1] != user:
sendMessage = "It's not your turn"
send_message = "It's not your turn"
self.bot.log("It wasn't their turn")
else:
player = game["turn"]
@ -215,7 +215,7 @@ class HexGame():
if board is None:
self.bot.log("It was an invalid position")
sendMessage = ("That's an invalid position. You must place your piece on an empty field.")
send_message = ("That's an invalid position. You must place your piece on an empty field.")
else:
# If the move is valid:
self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"board":board}})
@ -227,50 +227,50 @@ class HexGame():
winner = self.evaluateBoard(game["board"])[1]
if winner == 0: # Continue with the game.
gameWon = False
sendMessage = self.bot.databaseFuncs.getName(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.databaseFuncs.getName(game["players"][turn-1])+"'s turn."# The score is "+str(score)
game_won = False
send_message = self.bot.database_funcs.get_name(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.database_funcs.get_name(game["players"][turn-1])+"'s turn."# The score is "+str(score)
else: # Congratulations!
gameWon = True
game_won = True
self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":winner}})
sendMessage = self.bot.databaseFuncs.getName(game["players"][player-1])+" placed at "+position.upper()+" and won!"
send_message = self.bot.database_funcs.get_name(game["players"][player-1])+" placed at "+position.upper()+" and won!"
if game["players"][winner-1] != f"#{self.bot.user.id}":
winAmount = game["difficulty"]*10
sendMessage += " Adding "+str(winAmount)+" GwendoBucks to their account."
win_amount = game["difficulty"]*10
send_message += " Adding "+str(win_amount)+" GwendoBucks to their account."
self.bot.database["hex games"].update_one({"_id":channel},
{"$push":{"gameHistory":(int(position[1])-1, ord(position[0])-97)}})
# Is it now Gwendolyn's turn?
gwendoTurn = False
gwendolyn_turn = False
if game["players"][turn-1] == f"#{self.bot.user.id}":
self.bot.log("It's Gwendolyn's turn")
gwendoTurn = True
gwendolyn_turn = True
placedPiece = True
placed_piece = True
if user == f"#{self.bot.user.id}":
await ctx.channel.send(sendMessage)
await ctx.channel.send(send_message)
else:
await ctx.send(sendMessage)
await ctx.send(send_message)
if placedPiece:
if placed_piece:
# Update the board
self.draw.drawHexPlacement(channel,player, position)
with open(f"resources/games/oldImages/hex{channel}", "r") as f:
oldImage = await ctx.channel.fetch_message(int(f.read()))
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f:
old_image = await ctx.channel.fetch_message(int(f.read()))
if oldImage is not None:
await oldImage.delete()
if old_image is not None:
await old_image.delete()
else:
self.bot.log("The old image was already deleted")
self.bot.log("Sending the image")
filePath = f"resources/games/hexBoards/board{channel}.png"
oldImage = await ctx.channel.send(file = discord.File(filePath))
file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png"
old_image = await ctx.channel.send(file = discord.File(file_path))
if gameWon:
if game_won:
self.bot.log("Dealing with the winning player")
game = self.bot.database["hex games"].find_one({"_id":channel})
@ -281,10 +281,10 @@ class HexGame():
self.bot.database["hex games"].delete_one({"_id":channel})
else:
with open(f"resources/games/oldImages/hex{channel}", "w") as f:
f.write(str(oldImage.id))
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f:
f.write(str(old_image.id))
if gwendoTurn:
if gwendolyn_turn:
await self.hexAI(ctx)
# Returns a board where the placement has ocurred
@ -314,14 +314,14 @@ class HexGame():
game = self.bot.database["hex games"].find_one({"_id":channel})
if user not in game["players"]:
sendMessage = "You're not a player in the game"
send_message = "You're not a player in the game"
elif len(game["gameHistory"]) == 0:
sendMessage = "You can't undo nothing"
send_message = "You can't undo nothing"
elif user != game["players"][(game["turn"] % 2)]: # If it's not your turn
sendMessage = "It's not your turn"
send_message = "It's not your turn"
else:
turn = game["turn"]
self.bot.log("Undoing {}'s last move".format(self.bot.databaseFuncs.getName(user)))
self.bot.log("Undoing {}'s last move".format(self.bot.database_funcs.get_name(user)))
lastMove = game["gameHistory"].pop()
game["board"][lastMove[0]][lastMove[1]] = 0
@ -332,25 +332,25 @@ class HexGame():
# Update the board
self.draw.drawHexPlacement(channel,0,"abcdefghijk"[lastMove[1]]+str(lastMove[0]+1)) # The zero makes the hex disappear
sendMessage = f"You undid your last move at {lastMove}"
send_message = f"You undid your last move at {lastMove}"
undid = True
await ctx.send(sendMessage)
await ctx.send(send_message)
if undid:
with open(f"resources/games/oldImages/hex{channel}", "r") as f:
oldImage = await ctx.channel.fetch_message(int(f.read()))
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f:
old_image = await ctx.channel.fetch_message(int(f.read()))
if oldImage is not None:
await oldImage.delete()
if old_image is not None:
await old_image.delete()
else:
self.bot.log("The old image was already deleted")
self.bot.log("Sending the image")
filePath = f"resources/games/hexBoards/board{channel}.png"
oldImage = await ctx.channel.send(file = discord.File(filePath))
file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png"
old_image = await ctx.channel.send(file = discord.File(file_path))
with open(f"resources/games/oldImages/hex{channel}", "w") as f:
f.write(str(oldImage.id))
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f:
f.write(str(old_image.id))
# Plays as the AI
@ -417,20 +417,20 @@ class HexGame():
return scores[2]-scores[1], winner
def minimaxHex(self, board, depth, alpha, beta, maximizingPlayer):
def minimaxHex(self, board, depth, alpha, beta, maximizing_player):
# The depth is how many moves ahead the computer checks. This value is the difficulty.
if depth == 0 or 0 not in sum(board,[]):
score = self.evaluateBoard(board)[0]
return score
# if final depth is not reached, look another move ahead:
if maximizingPlayer: # red player predicts next move
if maximizing_player: # red player predicts next move
maxEval = -math.inf
possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0]
#self.bot.log("Judging a red move at depth {}".format(depth))
for i in possiblePlaces:
testBoard = copy.deepcopy(board)
testBoard[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 1 # because maximizingPlayer is Red which is number 1
evaluation = self.minimaxHex(testBoard,depth-1,alpha,beta,False)
test_board = copy.deepcopy(board)
test_board[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 1 # because maximizing_player is Red which is number 1
evaluation = self.minimaxHex(test_board,depth-1,alpha,beta,False)
maxEval = max(maxEval, evaluation)
alpha = max(alpha, evaluation)
if beta <= alpha:
@ -442,9 +442,9 @@ class HexGame():
possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0]
#self.bot.log("Judging a blue move at depth {}".format(depth))
for i in possiblePlaces:
testBoard = copy.deepcopy(board)
testBoard[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 2 # because minimizingPlayer is Blue which is number 2
evaluation = self.minimaxHex(testBoard,depth-1,alpha,beta,True)
test_board = copy.deepcopy(board)
test_board[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 2 # because minimizingPlayer is Blue which is number 2
evaluation = self.minimaxHex(test_board,depth-1,alpha,beta,True)
minEval = min(minEval, evaluation)
beta = min(beta, evaluation)
if beta <= alpha:
@ -474,7 +474,7 @@ class DrawHex():
self.HEXAGONHEIGHT = 1.5 * self.SIDELENGTH
self.FONTSIZE = 45
self.TEXTCOLOR = (0,0,0)
self.FONT = ImageFont.truetype('resources/fonts/futura-bold.ttf', self.FONTSIZE)
self.FONT = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', self.FONTSIZE)
self.LINETHICKNESS = 15
self.HEXTHICKNESS = 6 # This is half the width of the background lining between every hex
@ -490,7 +490,7 @@ class DrawHex():
self.COLYTHICKNESS = self.COLHEXTHICKNESS * math.sin(math.pi/6)
# The Name display things:
self.NAMESIZE = 60
self.NAMEFONT = ImageFont.truetype('resources/fonts/futura-bold.ttf', self.NAMESIZE)
self.NAMEFONT = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', self.NAMESIZE)
self.XNAME = {1:175, 2:self.CANVASWIDTH-100}
self.YNAME = {1:self.CANVASHEIGHT-150, 2:150}
self.NAMEHEXPADDING = 90
@ -556,7 +556,7 @@ class DrawHex():
game = self.bot.database["hex games"].find_one({"_id":channel})
for p in [1,2]:
playername = self.bot.databaseFuncs.getName(game["players"][p-1])
playername = self.bot.database_funcs.get_name(game["players"][p-1])
# Draw name
x = self.XNAME[p]
x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned
@ -573,13 +573,13 @@ class DrawHex():
(x, y+self.SMALLSIDELENGTH),
],fill = self.PIECECOLOR[p])
im.save("resources/games/hexBoards/board"+channel+".png")
im.save("gwendolyn/resources/games/hex_boards/board"+channel+".png")
def drawHexPlacement(self, channel,player,position):
FILEPATH = "resources/games/hexBoards/board"+channel+".png"
FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png"
self.bot.log(f"Drawing a newly placed hex. Filename: board{channel}.png")
# Translates position
@ -589,7 +589,7 @@ class DrawHex():
row = int(position[1:])-1
# Find the coordinates for the filled hex drawing
hexCoords = [
hex_coords = [
(self.BOARDCOORDINATES[row][column][0]+self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1] + self.COLYTHICKNESS),
(self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH/2, self.BOARDCOORDINATES[row][column][1]-0.5*self.SIDELENGTH + self.COLHEXTHICKNESS),
(self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH-self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1] + self.COLYTHICKNESS),
@ -603,7 +603,7 @@ class DrawHex():
with Image.open(FILEPATH) as im:
d = ImageDraw.Draw(im,"RGBA")
# Draws the hex piece
d.polygon(hexCoords,fill = self.PIECECOLOR[player], outline = self.BETWEENCOLOR)
d.polygon(hex_coords,fill = self.PIECECOLOR[player], outline = self.BETWEENCOLOR)
# Save
im.save(FILEPATH)
@ -611,7 +611,7 @@ class DrawHex():
self.bot.log("Error drawing new hex on board (error code 1541")
def drawSwap(self, channel):
FILEPATH = "resources/games/hexBoards/board"+channel+".png"
FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png"
game = self.bot.database["hex games"].find_one({"_id":channel})
# Opens the image
try:
@ -620,7 +620,7 @@ class DrawHex():
# Write player names and color
for p in [1,2]:
playername = self.bot.databaseFuncs.getName(game["players"][p%2])
playername = self.bot.database_funcs.get_name(game["players"][p%2])
x = self.XNAME[p]
x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned

View File

@ -0,0 +1,286 @@
"""
Contains functions relating to invest commands.
*Classes*
---------
Invest
Contains all the code for the invest commands.
"""
import discord # Used for embeds
from discord_slash.context import SlashContext # Used for type hints
class Invest():
"""
Contains all the invest functions.
*Methods*
---------
getPrice(symbol: str) -> int
getPortfolio(user: str) -> str
buyStock(user: str, stock: str: buyAmount: int) -> str
sellStock(user: str, stock: str, buyAmount: int) -> str
parseInvest(ctx: discord_slash.context.SlashContext,
parameters: str)
"""
def __init__(self, bot):
"""Initialize the class."""
self.bot = bot
def getPrice(self, symbol: str):
"""
Get the price of a stock.
*Parameters*
------------
symbol: str
The symbol of the stock to get the price of.
*Returns*
---------
price: int
The price of the stock.
"""
res = self.bot.finnhub_client.quote(symbol.upper())
if res == {}:
return 0
else:
return int(res["c"] * 100)
def getPortfolio(self, user: str):
"""
Get the stock portfolio of a user.
*Parameters*
------------
user: str
The id of the user to get the portfolio of.
*Returns*
---------
portfolio: str
The portfolio.
"""
investments_database = self.bot.database["investments"]
userInvestments = investments_database.find_one({"_id": user})
user_name = self.bot.database_funcs.get_name(user)
if userInvestments in [None, {}]:
return f"{user_name} does not have a stock portfolio."
else:
portfolio = f"**Stock portfolio for {user_name}**"
for key, value in list(userInvestments["investments"].items()):
purchaseValue = value["purchased for"]
stockPrice = self.getPrice(key)
valueAtPurchase = value["value at purchase"]
purchasedStock = value["purchased"]
valueChange = (stockPrice / valueAtPurchase)
currentValue = int(valueChange * purchasedStock)
portfolio += f"\n**{key}**: ___{currentValue} GwendoBucks___"
if purchaseValue != "?":
portfolio += f" (purchased for {purchaseValue})"
return portfolio
def buyStock(self, user: str, stock: str, buyAmount: int):
"""
Buy an amount of a specific stock.
*Paramaters*
------------
user: str
The id of the user buying.
stock: str
The symbol of the stock to buy.
buyAmount: int
The amount of GwendoBucks to use to buy the stock.
*Returns*
---------
send_message: str
The message to return to the user.
"""
if buyAmount < 100:
return "You cannot buy stocks for less than 100 GwendoBucks"
elif self.bot.money.checkBalance(user) < buyAmount:
return "You don't have enough money for that"
elif self.getPrice(stock) <= 0:
return f"{stock} is not traded on the american market."
else:
investments_database = self.bot.database["investments"]
stockPrice = self.getPrice(stock)
userInvestments = investments_database.find_one({"_id": user})
self.bot.money.addMoney(user, -1*buyAmount)
stock = stock.upper()
if userInvestments is not None:
userInvestments = userInvestments["investments"]
if stock in userInvestments:
value = userInvestments[stock]
valueChange = (stockPrice / value["value at purchase"])
currentValue = int(valueChange * value["purchased"])
newAmount = currentValue + buyAmount
value_path = f"investments.{stock}.value at purchase"
updater = {"$set": {value_path: stockPrice}}
investments_database.update_one({"_id": user}, updater)
purchased_path = f"investments.{stock}.purchased"
updater = {"$set": {purchased_path: newAmount}}
investments_database.update_one({"_id": user}, updater)
if value["purchased for"] != "?":
purchasedFor_path = f"investments.{stock}.purchased for"
updater = {"$set": {purchasedFor_path: buyAmount}}
investments_database.update_one({"_id": user}, updater)
else:
updater = {
"$set": {
"investments.{stock}": {
"purchased": buyAmount,
"value at purchase": stockPrice,
"purchased for": buyAmount
}
}
}
investments_database.update_one({"_id": user}, updater)
else:
new_user = {
"_id": user,
"investments": {
stock: {
"purchased": buyAmount,
"value at purchase": stockPrice,
"purchased for": buyAmount
}
}
}
investments_database.insert_one(new_user)
user_name = self.bot.database_funcs.get_name(user)
send_message = "{} bought {} GwendoBucks worth of {} stock"
send_message = send_message.format(user_name, buyAmount, stock)
return send_message
def sellStock(self, user: str, stock: str, sellAmount: int):
"""
Sell an amount of a specific stock.
*Paramaters*
------------
user: str
The id of the user selling.
stock: str
The symbol of the stock to sell.
buyAmount: int
The amount of GwendoBucks to sell for.
*Returns*
---------
send_message: str
The message to return to the user.
"""
if sellAmount <= 0:
return "no"
else:
investments_database = self.bot.database["investments"]
user_data = investments_database.find_one({"_id": user})
userInvestments = user_data["investments"]
stock = stock.upper()
if userInvestments is not None and stock in userInvestments:
value = userInvestments[stock]
stockPrice = self.getPrice(stock)
priceChange = (stockPrice / value["value at purchase"])
purchasedAmount = int(priceChange * value["purchased"])
purchased_path = f"investments.{stock}.purchased"
updater = {"$set": {purchased_path: purchasedAmount}}
investments_database.update_one({"_id": user}, updater)
valueAtPurchase_path = f"investments.{stock}.value at purchase"
updater = {"$set": {valueAtPurchase_path: stockPrice}}
investments_database.update_one({"_id": user}, updater)
if value["purchased"] >= sellAmount:
self.bot.money.addMoney(user, sellAmount)
if sellAmount < value["purchased"]:
purchased_path = f"investments.{stock}.purchased"
updater = {"$inc": {purchased_path: -sellAmount}}
investments_database.update_one({"_id": user}, updater)
purchasedFor_path = f"investments.{stock}.purchased for"
updater = {"$set": {purchasedFor_path: "?"}}
investments_database.update_one({"_id": user}, updater)
else:
updater = {"$unset": {f"investments.{stock}": ""}}
investments_database.update_one({"_id": user}, updater)
user_name = self.bot.database_funcs.get_name(user)
send_message = "{} sold {} GwendoBucks worth of {} stock"
return send_message.format(user_name, sellAmount, stock)
else:
return f"You don't have enough {stock} stocks to do that"
else:
return f"You don't have any {stock} stock"
async def parseInvest(self, ctx: SlashContext, parameters: str):
"""
Parse an invest command. TO BE DELETED.
*Parameters*
------------
ctx: discord_slash.context.SlashContext
The context of the slash command.
parameters: str
The parameters of the command.
"""
await self.bot.defer(ctx)
user = f"#{ctx.author.id}"
if parameters.startswith("check"):
commands = parameters.split(" ")
if len(commands) == 1:
response = self.getPortfolio(user)
else:
price = self.getPrice(commands[1])
if price == 0:
response = "{} is not traded on the american market."
response = response.format(commands[0].upper())
else:
price = f"{price:,}".replace(",", ".")
response = self.bot.long_strings["Stock value"]
response = response.format(commands[1].upper(), price)
elif parameters.startswith("buy"):
commands = parameters.split(" ")
if len(commands) == 3:
response = self.buyStock(user, commands[1], int(commands[2]))
else:
response = self.bot.long_strings["Stock parameters"]
elif parameters.startswith("sell"):
commands = parameters.split(" ")
if len(commands) == 3:
response = self.sellStock(user, commands[1], int(commands[2]))
else:
response = self.bot.long_strings["Stock parameters"]
else:
response = "Incorrect parameters"
if response.startswith("**"):
responses = response.split("\n")
text = "\n".join(responses[1:])
embedParams = {
"title": responses[0],
"description": text,
"colour": 0x00FF00
}
em = discord.Embed(**embedParams)
await ctx.send(embed=em)
else:
await ctx.send(response)

View File

@ -0,0 +1,151 @@
"""
Contains the code that deals with money.
*Classes*
---------
Money
Deals with money.
"""
import discord_slash # Used for typehints
import discord # Used for typehints
class Money():
"""
Deals with money.
*Methods*
---------
checkBalance(user: str)
sendBalance(ctx: discord_slash.context.SlashContext)
addMoney(user: str, amount: int)
giveMoney(ctx: discord_slash.context.SlashContext, user: discord.User,
amount: int)
*Attributes*
------------
bot: Gwendolyn
The instance of Gwendolyn
database: pymongo.Client
The mongo database
"""
def __init__(self, bot):
"""Initialize the class."""
self.bot = bot
self.database = bot.database
def checkBalance(self, user: str):
"""
Get the account balance of a user.
*Parameters*
------------
user: str
The user to get the balance of.
*Returns*
---------
balance: int
The balance of the user's account.
"""
self.bot.log("checking "+user+"'s account balance")
user_data = self.database["users"].find_one({"_id": user})
if user_data is not None:
return user_data["money"]
else:
return 0
async def sendBalance(self, ctx: discord_slash.context.SlashContext):
"""
Get your own account balance.
*Parameters*
------------
ctx: discord_slash.context.SlashContext
The context of the command.
"""
await self.bot.defer(ctx)
response = self.checkBalance("#"+str(ctx.author.id))
user_name = ctx.author.display_name
if response == 1:
new_message = f"{user_name} has {response} GwendoBuck"
else:
new_message = f"{user_name} has {response} GwendoBucks"
await ctx.send(new_message)
# Adds money to the account of a user
def addMoney(self, user: str, amount: int):
"""
Add money to a user account.
*Parameters*
------------
user: str
The id of the user to give money.
amount: int
The amount to add to the user's account.
"""
self.bot.log("adding "+str(amount)+" to "+user+"'s account")
user_data = self.database["users"].find_one({"_id": user})
if user_data is not None:
updater = {"$inc": {"money": amount}}
self.database["users"].update_one({"_id": user}, updater)
else:
new_user = {
"_id": user,
"user name": self.bot.database_funcs.get_name(user),
"money": amount
}
self.database["users"].insert_one(new_user)
# Transfers money from one user to another
async def giveMoney(self, ctx: discord_slash.context.SlashContext,
user: discord.User, amount: int):
"""
Give someone else money from your account.
*Parameters*
------------
ctx: discord_slash.context.SlashContext
The context of the command.
user: discord.User
The user to give money.
amount: int
The amount to transfer.
"""
await self.bot.defer(ctx)
username = user.display_name
if self.bot.database_funcs.get_id(username) is None:
async for member in ctx.guild.fetch_members(limit=None):
if member.display_name.lower() == username.lower():
username = member.display_name
user_id = f"#{member.id}"
new_user = {
"_id": user_id,
"user name": username,
"money": 0
}
self.bot.database["users"].insert_one(new_user)
userid = f"#{ctx.author.id}"
user_data = self.database["users"].find_one({"_id": userid})
targetUser = self.bot.database_funcs.get_id(username)
if amount <= 0:
self.bot.log("They tried to steal")
await ctx.send("Yeah, no. You can't do that")
elif targetUser is None:
self.bot.log("They weren't in the system")
await ctx.send("The target doesn't exist")
elif user_data is None or user_data["money"] < amount:
self.bot.log("They didn't have enough GwendoBucks")
await ctx.send("You don't have that many GwendoBuck")
else:
self.addMoney(f"#{ctx.author.id}", -1 * amount)
self.addMoney(targetUser, amount)
await ctx.send(f"Transferred {amount} GwendoBucks to {username}")

View File

@ -0,0 +1,205 @@
"""
Contains code for trivia games.
*Classes*
---------
Trivia
Contains all the code for the trivia commands.
"""
import urllib # Used to get data from api
import json # Used to read data from api
import random # Used to shuffle answers
import asyncio # Used to sleep
from discord_slash.context import SlashContext # Used for type hints
class Trivia():
"""
Contains the code for trivia games.
*Methods*
---------
triviaStart(channel: str) -> str, str, str
triviaAnswer(user: str, channel: str, command: str) -> str
triviaCountPoints(channel: str)
triviaParse(ctx: SlashContext, answer: str)
"""
def __init__(self, bot):
"""Initialize the class."""
self.bot = bot
def triviaStart(self, channel: str):
"""
Start a game of trivia.
Downloads a question with answers, shuffles the wrong answers
with the correct answer and returns the questions and answers.
Also saves the question in the database.
*Parameters*
------------
channel: str
The id of the channel to start the game in
*Returns*
---------
send_message: str
The message to return to the user.
"""
triviaQuestions = self.bot.database["trivia questions"]
question = triviaQuestions.find_one({"_id": channel})
self.bot.log(f"Trying to find a trivia question for {channel}")
if question is None:
apiUrl = "https://opentdb.com/api.php?amount=10&type=multiple"
with urllib.request.urlopen(apiUrl) as response:
data = json.loads(response.read())
question = data["results"][0]["question"]
self.bot.log(f"Found the question \"{question}\"")
answers = data["results"][0]["incorrect_answers"]
answers.append(data["results"][0]["correct_answer"])
random.shuffle(answers)
correctAnswer = data["results"][0]["correct_answer"]
correctAnswer = answers.index(correctAnswer) + 97
newQuestion = {
"_id": channel,
"answer": str(chr(correctAnswer)),
"players": {}
}
triviaQuestions.insert_one(newQuestion)
replacements = {
"&#039;": "\'",
"&quot;": "\"",
"&ldquo;": "\"",
"&rdquo;": "\"",
"&eacute;": "é"
}
question = data["results"][0]["question"]
for key, value in replacements.items():
question = question.replace(key, value)
for answer in answers:
for key, value in replacements.items():
answer = answer.replace(key, value)
return question, answers, correctAnswer
else:
log_message = "There was already a trivia question for that channel"
self.bot.log(log_message)
return self.bot.long_strings["Trivia going on"], "", ""
def triviaAnswer(self, user: str, channel: str, command: str):
"""
Answer the current trivia question.
*Parameters*
------------
user: str
The id of the user who answered.
channel: str
The id of the channel the game is in.
command: str
The user's answer.
*Returns*
---------
send_message: str
The message to send if the function failed.
"""
triviaQuestions = self.bot.database["trivia questions"]
question = triviaQuestions.find_one({"_id": channel})
if command not in ["a", "b", "c", "d"]:
self.bot.log("I didn't quite understand that")
return "I didn't quite understand that"
elif question is None:
self.bot.log("There's no question right now")
return "There's no question right now"
elif user in question["players"]:
self.bot.log(f"{user} has already answered this question")
return f"{user} has already answered this question"
else:
self.bot.log(f"{user} answered the question in {channel}")
updater = {"$set": {f"players.{user}": command}}
triviaQuestions.update_one({"_id": channel}, updater)
return None
def triviaCountPoints(self, channel: str):
"""
Add money to every winner's account.
*Parameters*
------------
channel: str
The id of the channel the game is in.
"""
triviaQuestions = self.bot.database["trivia questions"]
question = triviaQuestions.find_one({"_id": channel})
self.bot.log("Counting points for question in "+channel)
if question is not None:
for player, answer in question["players"].items():
if answer == question["answer"]:
self.bot.money.addMoney(player, 1)
else:
self.bot.log("Couldn't find the questio")
return None
async def triviaParse(self, ctx: SlashContext, answer: str):
"""
Parse a trivia command.
*Parameters*
------------
ctx: SlashContext
The context of the command.
answer: str
The answer, if any.
"""
await self.bot.defer(ctx)
channelId = str(ctx.channel_id)
if answer == "":
question, options, correctAnswer = self.triviaStart(channelId)
if options != "":
results = "**"+question+"**\n"
for x, option in enumerate(options):
results += chr(x+97) + ") "+option+"\n"
await ctx.send(results)
await asyncio.sleep(60)
self.triviaCountPoints(channelId)
delete_gameParams = ["trivia questions", channelId]
self.bot.database_funcs.delete_game(*delete_gameParams)
self.bot.log("Time's up for the trivia question", channelId)
send_message = self.bot.long_strings["Trivia time up"]
format_parameters = [chr(correctAnswer), options[correctAnswer-97]]
send_message = send_message.format(*format_parameters)
await ctx.send(send_message)
else:
await ctx.send(question, hidden=True)
elif answer in ["a", "b", "c", "d"]:
userId = f"#{ctx.author.id}"
response = self.triviaAnswer(userId, channelId, answer)
if response is None:
user_name = ctx.author.display_name
await ctx.send(f"{user_name} answered **{answer}**")
else:
await ctx.send(response)
else:
self.bot.log("I didn't understand that", channelId)
await ctx.send("I didn't understand that")

View File

@ -2,4 +2,4 @@
__all__ = ["LookupFuncs"]
from .lookupFuncs import LookupFuncs
from .lookup_funcs import LookupFuncs

View File

@ -2,7 +2,7 @@ import math
import json
import discord
from utils import cap
from gwendolyn.utils import cap
class LookupFuncs():
@ -29,7 +29,7 @@ class LookupFuncs():
await ctx.send("I don't know that monster...")
else:
# Opens "monsters.json"
data = json.load(open('resources/lookup/monsters.json', encoding = "utf8"))
data = json.load(open('gwendolyn/resources/lookup/monsters.json', encoding = "utf8"))
for monster in data:
if "name" in monster and str(query) == monster["name"]:
self.bot.log("Found it!")
@ -149,16 +149,16 @@ class LookupFuncs():
self.bot.log("Looking up "+query)
# Opens "spells.json"
data = json.load(open('resources/lookup/spells.json', encoding = "utf8"))
data = json.load(open('gwendolyn/resources/lookup/spells.json', encoding = "utf8"))
if query in data:
self.bot.log("Returning spell information")
sendMessage = (f"***{query}***\n*{data[query]['level']} level {data[query]['school']}\nCasting Time: {data[query]['casting_time']}\nRange:{data[query]['range']}\nComponents:{data[query]['components']}\nDuration:{data[query]['duration']}*\n \n{data[query]['description']}")
send_message = (f"***{query}***\n*{data[query]['level']} level {data[query]['school']}\nCasting Time: {data[query]['casting_time']}\nRange:{data[query]['range']}\nComponents:{data[query]['components']}\nDuration:{data[query]['duration']}*\n \n{data[query]['description']}")
else:
self.bot.log("I don't know that spell (error code 501)")
sendMessage = "I don't think that's a spell (error code 501)"
send_message = "I don't think that's a spell (error code 501)"
if len(sendMessage) > 2000:
await ctx.send(sendMessage[:2000])
await ctx.send(sendMessage[2000:])
if len(send_message) > 2000:
await ctx.send(send_message[:2000])
await ctx.send(send_message[2000:])
else:
await ctx.send(sendMessage)
await ctx.send(send_message)

View File

@ -17,7 +17,7 @@ class Generators():
# Generates a random name
async def nameGen(self, ctx):
# Makes a list of all names from "names.txt"
names = open('resources/names.txt', encoding='utf8').read()
names = open('gwendolyn/resources/names.txt', encoding='utf8').read()
corpus = list(names)
# Makes a list of pairs
@ -64,7 +64,7 @@ class Generators():
if random.randint(1,10) > 1:
try:
new_letter = random.choice(letter_dict[chain[-2]+chain[-1]])
except:
except KeyError():
new_letter = random.choice(letter_dict[chain[-1]])
else:
new_letter = random.choice(letter_dict[chain[-1]])
@ -72,15 +72,15 @@ class Generators():
# Ends name if the name ends
if new_letter == "\n":
done = True
genName = "".join(chain)
self.bot.log("Generated "+genName[:-1])
gen_name = "".join(chain)
self.bot.log("Generated "+gen_name[:-1])
# Returns the name
await ctx.send(genName)
await ctx.send(gen_name)
# Generates a random tavern name
async def tavernGen(self, ctx):
# Lists first parts, second parts and third parts of tavern names
# _lists first parts, second parts and third parts of tavern names
fp = ["The Silver","The Golden","The Staggering","The Laughing","The Prancing","The Gilded","The Running","The Howling","The Slaughtered","The Leering","The Drunken","The Leaping","The Roaring","The Frowning","The Lonely","The Wandering","The Mysterious","The Barking","The Black","The Gleaming","The Tap-Dancing","The Sad","The Sexy","The Artificial","The Groovy","The Merciful","The Confused","The Pouting","The Horny","The Okay","The Friendly","The Hungry","The Handicapped","The Fire-breathing","The One-Eyed","The Psychotic","The Mad","The Evil","The Idiotic","The Trusty","The Busty"]
sp = ["Eel","Dolphin","Dwarf","Pegasus","Pony","Rose","Stag","Wolf","Lamb","Demon","Goat","Spirit","Horde","Jester","Mountain","Eagle","Satyr","Dog","Spider","Star","Dad","Rat","Jeremy","Mouse","Unicorn","Pearl","Ant","Crab","Penguin","Octopus","Lawyer","Ghost","Toad","Handjob","Immigrant","SJW","Dragon","Bard","Sphinx","Soldier","Salmon","Owlbear","Kite","Frost Giant","Arsonist"]
tp = [" Tavern"," Inn","","","","","","","","",""]

View File

@ -8,9 +8,9 @@ class NerdShit():
async def wolfSearch(self,ctx,content):
await self.bot.defer(ctx)
fnt = ImageFont.truetype('resources/fonts/times-new-roman.ttf', 20)
fnt = ImageFont.truetype('gwendolyn/resources/fonts/times-new-roman.ttf', 20)
self.bot.log("Requesting data")
bot = wolframalpha.Client(self.bot.credentials.wolfKey)
bot = wolframalpha.Client(self.bot.credentials["wolfram_alpha_key"])
res = bot.query(content)
self.bot.log("Processing data")
@ -38,10 +38,10 @@ class NerdShit():
heights += [height]
width = max(width,int(pod.img['@width']))
if titleChucks[x][count] == "":
placeForText = 0
placeFor_text = 0
else:
placeForText = 30
height += int(pod.img["@height"]) + 10 + placeForText
placeFor_text = 30
height += int(pod.img["@height"]) + 10 + placeFor_text
width += 10
height += 5
@ -49,33 +49,33 @@ class NerdShit():
for count, pod in enumerate(chunk):
response = requests.get(pod.img["@src"])
file = open("resources/wolfTemp.png", "wb")
file = open("gwendolyn/resources/wolfTemp.png", "wb")
file.write(response.content)
file.close()
oldImage = Image.open("resources/wolfTemp.png")
oldSize = oldImage.size
old_image = Image.open("gwendolyn/resources/wolfTemp.png")
oldSize = old_image.size
if titleChucks[x][count] == "":
placeForText = 0
placeFor_text = 0
else:
placeForText = 30
newSize = (width,int(oldSize[1]+10+placeForText))
newImage = Image.new("RGB",newSize,color=(255,255,255))
newImage.paste(oldImage, (int((int(oldSize[0]+10)-oldSize[0])/2),int(((newSize[1]-placeForText)-oldSize[1])/2)+placeForText))
placeFor_text = 30
newSize = (width,int(oldSize[1]+10+placeFor_text))
new_image = Image.new("RGB",newSize,color=(255,255,255))
new_image.paste(old_image, (int((int(oldSize[0]+10)-oldSize[0])/2),int(((newSize[1]-placeFor_text)-oldSize[1])/2)+placeFor_text))
if titleChucks[x][count] != "":
d = ImageDraw.Draw(newImage,"RGB")
d = ImageDraw.Draw(new_image,"RGB")
d.text((5,7),titleChucks[x][count],font=fnt,fill=(150,150,150))
wolfImage.paste(newImage,(0,heights[count]))
newImage.close()
oldImage.close()
wolfImage.paste(new_image,(0,heights[count]))
new_image.close()
old_image.close()
count += 1
wolfImage.save("resources/wolf.png")
wolfImage.save("gwendolyn/resources/wolf.png")
wolfImage.close()
await ctx.channel.send(file = discord.File("resources/wolf.png"))
await ctx.channel.send(file = discord.File("gwendolyn/resources/wolf.png"))
os.remove("resources/wolf.png")
os.remove("resources/wolfTemp.png")
os.remove("gwendolyn/resources/wolf.png")
os.remove("gwendolyn/resources/wolfTemp.png")
else:
self.bot.log("No returned data")
await ctx.send("Could not find anything relating to your search")

View File

@ -7,11 +7,11 @@ import lxml # Used in imageFunc
import fandom # Used in findWikiPage
import d20 # Used in rollDice
import ast
from .bedreNetflix import BedreNetflix
from .nerdShit import NerdShit
from .plex import Plex
from .nerd_shit import NerdShit
from .generators import Generators
from utils import cap
from gwendolyn.utils import cap
fandom.set_lang("da")
fandom.set_wiki("senkulpa")
@ -19,17 +19,17 @@ fandom.set_wiki("senkulpa")
class MyStringifier(d20.MarkdownStringifier):
def _str_expression(self, node):
if node.comment == None:
resultText = "Result"
result_text = "Result"
else:
resultText = node.comment.capitalize()
result_text = node.comment.capitalize()
return f"**{resultText}**: {self._stringify(node.roll)}\n**Total**: {int(node.total)}"
return f"**{result_text}**: {self._stringify(node.roll)}\n**Total**: {int(node.total)}"
class Other():
def __init__(self, bot):
self.bot = bot
self.bedreNetflix = BedreNetflix(self.bot)
self.nerdShit = NerdShit(self.bot)
self.plex = Plex(self.bot)
self.nerd_shit = NerdShit(self.bot)
self.generators = Generators(self.bot)
# Picks a random movie and returns information about it
@ -40,12 +40,12 @@ class Other():
imdbClient = imdb.IMDb()
self.bot.log("Picking a movie")
with open("resources/movies.txt", "r") as f:
movieList = f.read().split("\n")
movieName = random.choice(movieList)
with open("gwendolyn/resources/movies.txt", "r") as f:
movie_list = f.read().split("\n")
movie_name = random.choice(movie_list)
self.bot.log(f"Searching for {movieName}")
searchResult = imdbClient.search_movie(movieName)
self.bot.log(f"Searching for {movie_name}")
searchResult = imdbClient.search_movie(movie_name)
self.bot.log("Getting the data")
movie = searchResult[0]
@ -74,17 +74,17 @@ class Other():
author = ctx.author.display_name
now = datetime.datetime.now()
if time_in_range(now.replace(hour=5, minute=0, second=0, microsecond=0),now.replace(hour=10, minute=0, second=0, microsecond=0), now):
sendMessage = "Good morning, "+str(author)
send_message = "Good morning, "+str(author)
elif time_in_range(now.replace(hour=13, minute=0, second=0, microsecond=0),now.replace(hour=18, minute=0, second=0, microsecond=0), now):
sendMessage = "Good afternoon, "+str(author)
send_message = "Good afternoon, "+str(author)
elif time_in_range(now.replace(hour=18, minute=0, second=0, microsecond=0),now.replace(hour=22, minute=0, second=0, microsecond=0), now):
sendMessage = "Good evening, "+str(author)
send_message = "Good evening, "+str(author)
elif time_in_range(now.replace(hour=22, minute=0, second=0, microsecond=0),now.replace(hour=23, minute=59, second=59, microsecond=0), now):
sendMessage = "Good night, "+str(author)
send_message = "Good night, "+str(author)
else:
sendMessage = "Hello, "+str(author)
send_message = "Hello, "+str(author)
await ctx.send(sendMessage)
await ctx.send(send_message)
# Finds a random picture online
async def imageFunc(self, ctx):
@ -183,13 +183,13 @@ class Other():
async def helpFunc(self, ctx, command):
if command == "":
with open("resources/help/help.txt",encoding="utf-8") as f:
with open("gwendolyn/resources/help/help.txt",encoding="utf-8") as f:
text = f.read()
em = discord.Embed(title = "Help", description = text,colour = 0x59f442)
await ctx.send(embed = em)
else:
self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id))
with open(f"resources/help/help-{command}.txt",encoding="utf-8") as f:
with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as f:
text = f.read()
em = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442)
await ctx.send(embed = em)

View File

@ -0,0 +1,536 @@
"""Plex integration with the bot."""
from math import floor, ceil
import time
import json
import asyncio
import requests
import imdb
import discord
class Plex():
"""Container for Plex functions and commands."""
def __init__(self,bot):
self.bot = bot
self.credentials = self.bot.credentials
self.long_strings = self.bot.long_strings
server_ip = ["localhost", "192.168.0.40"][self.bot.options["testing"]]
self.radarr_url = "http://"+server_ip+":7878/api/v3/"
self.sonarr_url = "http://"+server_ip+":8989/api/"
self.qbittorrent_url = "http://"+server_ip+":8080/api/v2/"
self.movie_path = "/media/plex/Server/movies/"
self.show_path = "/media/plex/Server/Shows/"
async def request_movie(self, ctx, movie_name):
"""Request a movie for the Plex Server"""
await self.bot.defer(ctx)
self.bot.log("Searching for "+movie_name)
movie_list = imdb.IMDb().search_movie(movie_name)
movies = []
for movie in movie_list:
if movie["kind"] == "movie":
movies.append(movie)
if len(movies) > 5:
movies = movies[:5]
if len(movies) == 1:
message_title = "**Is it this movie?**"
else:
message_title = "**Is it any of these movies?**"
message_text = ""
imdb_ids = []
for i, movie in enumerate(movies):
try:
message_text += "\n"+str(i+1)+") "+movie["title"]
try:
message_text += " ("+str(movie["year"])+")"
except KeyError:
self.bot.log(f"{movie['title']} has no year.")
except KeyError:
message_text += "Error"
imdb_ids.append(movie.movieID)
self.bot.log(
f"Returning a list of {len(movies)} possible movies: {imdb_ids}"
)
embed = discord.Embed(
title=message_title,
description=message_text,
colour=0x00FF00
)
message = await ctx.send(embed=embed)
message_data = {"message_id":message.id,"imdb_ids":imdb_ids}
file_path = f"gwendolyn/resources/plex/old_message{ctx.channel.id}"
with open(file_path,"w") as file_pointer:
json.dump(message_data, file_pointer)
if len(movies) == 1:
await message.add_reaction("✔️")
else:
for i in range(len(movies)):
await message.add_reaction(["1","2","3","4","5"][i])
await message.add_reaction("")
message = await ctx.channel.fetch_message(message.id)
if (message.content != "" and
not isinstance(ctx.channel, discord.DMChannel)):
await message.clear_reactions()
async def add_movie(self, message, imdb_id, edit_message = True):
"""Add a movie to Plex server."""
if imdb_id is None:
self.bot.log("Did not find what the user was searching for")
if edit_message:
await message.edit(
embed = None,
content = "Try searching for the IMDB id"
)
else:
await message.channel.send("Try searching for the IMDB id")
else:
self.bot.log("Trying to add movie "+str(imdb_id))
api_key = self.credentials["radarr_key"]
request_url = self.radarr_url+"movie/lookup/imdb?imdbId=tt"+imdb_id
request_url += "&apiKey="+api_key
response = requests.get(request_url)
lookup_data = response.json()
post_data = {"qualityProfileId": 1,
"rootFolder_path" : self.movie_path,
"monitored" : True,
"addOptions": {"searchForMovie": True}}
for key in ["tmdbId","title","titleSlug","images","year"]:
post_data.update({key : lookup_data[key]})
response = requests.post(
url= self.radarr_url+"movie?apikey="+api_key,
json = post_data
)
if response.status_code == 201:
success_message = "{} successfully added to Plex".format(
post_data["title"]
)
if edit_message:
await message.edit(
embed = None,
content = success_message
)
else:
await message.channel.send(success_message)
self.bot.log("Added "+post_data["title"]+" to Plex")
elif response.status_code == 400:
fail_text = self.long_strings["Already on Plex"].format(
post_data['title']
)
if edit_message:
await message.edit(embed = None, content = fail_text)
else:
await message.channel.send(fail_text)
else:
if edit_message:
await message.edit(
embed = None,
content = "Something went wrong"
)
else:
await message.channel.send("Something went wrong")
self.bot.log(str(response.status_code)+" "+response.reason)
async def request_show(self, ctx, show_name):
"""Request a show for the Plex server."""
await self.bot.defer(ctx)
self.bot.log("Searching for "+show_name)
movies = imdb.IMDb().search_movie(show_name) # Replace with tvdb
shows = []
for movie in movies:
if movie["kind"] in ["tv series","tv miniseries"]:
shows.append(movie)
if len(shows) > 5:
shows = shows[:5]
if len(shows) == 1:
message_title = "**Is it this show?**"
else:
message_title = "**Is it any of these shows?**"
message_text = ""
imdb_names = []
for i, show in enumerate(shows):
try:
message_text += f"\n{i+1}) {show['title']} ({show['year']})"
except KeyError:
try:
message_text += "\n"+str(i+1)+") "+show["title"]
except KeyError:
message_text += "Error"
imdb_names.append(show["title"])
self.bot.log(
f"Returning a list of {len(shows)} possible shows: {imdb_names}"
)
embed = discord.Embed(
title=message_title,
description=message_text,
colour=0x00FF00
)
message = await ctx.send(embed=embed)
message_data = {"message_id":message.id,"imdb_names":imdb_names}
file_path = "gwendolyn/resources/plex/old_message"+str(ctx.channel.id)
with open(file_path,"w") as file_pointer:
json.dump(message_data, file_pointer)
if len(shows) == 1:
await message.add_reaction("✔️")
else:
for i in range(len(shows)):
await message.add_reaction(["1","2","3","4","5"][i])
await message.add_reaction("")
message = await ctx.channel.fetch_message(message.id)
if message.content != "":
if not isinstance(ctx.channel, discord.DMChannel):
await message.clear_reactions()
async def add_show(self, message, imdb_name):
"""Add the requested show to Plex."""
if imdb_name is None:
self.bot.log("Did not find what the user was searching for")
await message.edit(
embed = None,
content = "Try searching for the IMDB id"
)
else:
self.bot.log("Trying to add show "+str(imdb_name))
api_key = self.credentials["sonarr_key"]
request_url = self.sonarr_url+"series/lookup?term="
request_url += imdb_name.replace(" ","%20")
request_url += "&apiKey="+api_key
response = requests.get(request_url)
lookup_data = response.json()[0]
post_data = {
"ProfileId" : 1,
"rootFolder_path" : self.show_path,
"monitored" : True,
"addOptions" : {"searchForMissingEpisodes" : True}
}
for key in ["tvdbId","title","titleSlug","images","seasons"]:
post_data.update({key : lookup_data[key]})
response = requests.post(
url= self.sonarr_url+"series?apikey="+api_key,
json = post_data
)
if response.status_code == 201:
await message.edit(
embed = None,
content = post_data["title"]+" successfully added to Plex"
)
self.bot.log("Added a "+post_data["title"]+" to Plex")
elif response.status_code == 400:
text = self.long_strings["Already on Plex"].format(
post_data['title']
)
await message.edit(embed = None, content = text)
else:
await message.edit(
embed = None,
content = "Something went wrong"
)
self.bot.log(str(response.status_code)+" "+response.reason)
async def __generate_download_list(self, show_dm, show_movies, show_shows,
episodes):
"""Generate a list of all torrents.
*Returns*
message_text: str
A formatted list of all torrents
all_downloaded: bool
Whether all torrents are downloaded
"""
self.bot.log("Generating torrent list")
title_width = 100
message = []
all_downloaded = True
if show_dm:
message.append("")
dm_section_title = "*Torrent Downloads*"
dm_section_title_line = "-"*((title_width-len(dm_section_title))//2)
message.append(
dm_section_title_line+dm_section_title+dm_section_title_line
)
response = requests.get(self.qbittorrent_url+"torrents/info")
torrent_list = response.json()
if len(torrent_list) > 0:
for torrent in torrent_list:
torrent_name = torrent["name"]
if len(torrent_name) > 30:
if torrent_name[26] == " ":
torrent_name = torrent_name[:26]+"...."
else:
torrent_name = torrent_name[:27]+"..."
while len(torrent_name) < 30:
torrent_name += " "
if torrent["size"] == 0:
download_ratio = 0
elif torrent["amount_left"] == 0:
download_ratio = 1
else:
download_ratio = min(
torrent["downloaded"]/torrent["size"],
1
)
progress_bar = "|"+(""*floor(download_ratio*20))
while len(progress_bar) < 21:
progress_bar += " "
progress_bar += "| "+str(floor(download_ratio*100))+"%"
while len(progress_bar) < 27:
progress_bar += " "
eta_in_seconds = torrent["eta"]
if eta_in_seconds >= 8640000:
eta = ""
else:
eta = ""
if eta_in_seconds >= 86400:
eta += str(floor(eta_in_seconds/86400))+"d "
if eta_in_seconds >= 3600:
eta += str(floor((eta_in_seconds%86400)/3600))+"h "
if eta_in_seconds >= 60:
eta += str(floor((eta_in_seconds%3600)/60))+"m "
eta += str(eta_in_seconds%60)+"s"
torrent_info = f"{torrent_name} {progress_bar} "
torrent_info += f"(Eta: {eta})"
if torrent["state"] == "stalledDL":
torrent_info += " (Stalled)"
if not (download_ratio == 1 and
torrent["last_activity"] < time.time()-7200):
message.append(torrent_info)
if download_ratio < 1 and torrent["state"] != "stalledDL":
all_downloaded = False
else:
message.append("No torrents currently downloading")
if show_movies:
message.append("")
movies_section_title = "*Missing movies not downloading*"
movies_section_line = (
"-"*((title_width-len(movies_section_title))//2)
)
message.append(
movies_section_line+movies_section_title+movies_section_line
)
movie_list = requests.get(
self.radarr_url+"movie?api_key="+self.credentials["radarr_key"]
).json()
movie_queue = requests.get(
self.radarr_url+"queue?api_key="+self.credentials["radarr_key"]
).json()
movie_queue_ids = []
for queue_item in movie_queue["records"]:
movie_queue_ids.append(queue_item["movieId"])
for movie in movie_list:
if (not movie["hasFile"] and
movie["id"] not in movie_queue_ids):
movie_name = movie["title"]
if len(movie_name) > 40:
if movie_name[36] == " ":
movie_name = movie_name[:36]+"...."
else:
movie_name = movie_name[:37]+"..."
while len(movie_name) < 41:
movie_name += " "
if movie["monitored"]:
movie_info = movie_name+"Could not find a torrent"
else:
movie_info = self.long_strings["No torrent"].format(
movie_name
)
message.append(movie_info)
if show_shows:
message.append("")
show_section_title = "*Missing shows not downloading*"
show_section_line = "-"*((title_width-len(show_section_title))//2)
message.append(
show_section_line+show_section_title+show_section_line
)
show_list = requests.get(
self.sonarr_url+"series?api_key="+self.credentials["sonarr_key"]
).json()
for show in show_list:
if show["seasons"][0]["seasonNumber"] == 0:
seasons = show["seasons"][1:]
else:
seasons = show["seasons"]
if any(
(
i["statistics"]["episodeCount"] !=
i["statistics"]["totalEpisodeCount"]
) for i in seasons):
if all(
i["statistics"]["episodeCount"] == 0 for i in seasons
):
message.append(show["title"] + " (all episodes)")
else:
if episodes:
missing_episodes = sum(
(i["statistics"]["totalEpisodeCount"] -
i["statistics"]["episodeCount"])
for i in seasons)
message.append(
f"{show['title']} ({missing_episodes} episodes)"
)
message.append("-"*title_width)
message_text = "```"+"\n".join(message[1:])+"```"
if message_text == "``````":
message_text = self.long_strings["No torrents downloading"]
return message_text, all_downloaded
async def downloading(self, ctx, content):
"""Send message with list of all downloading torrents."""
async def send_long_message(ctx,message_text):
if len(message_text) <= 1994:
await ctx.send("```"+message_text+"```")
else:
cut_off_index = message_text[:1994].rfind("\n")
await ctx.send("```"+message_text[:cut_off_index]+"```")
await send_long_message(ctx,message_text[cut_off_index+1:])
await self.bot.defer(ctx)
# showDM, showMovies, showShows, episodes
parameters = [False, False, False, False]
show_dm_args = ["d", "dm", "downloading", "downloadmanager"]
show_movies_args = ["m", "movies"]
show_shows_args = ["s", "shows", "series"]
show_episode_args = ["e", "episodes"]
arg_list = [
show_dm_args, show_movies_args, show_shows_args, show_episode_args
]
input_args = []
valid_arguments = True
while content != "" and valid_arguments:
if content[0] == " ":
content = content[1:]
elif content[0] == "-":
if content[1] == "-":
arg_start = 2
if " " in content:
arg_stop = content.find(" ")
else:
arg_stop = None
else:
arg_start = 1
arg_stop = 2
input_args.append(content[arg_start:arg_stop])
if arg_stop is None:
content = ""
else:
content = content[arg_stop:]
else:
valid_arguments = False
if valid_arguments:
for arg_index, arg_aliases in enumerate(arg_list):
arg_in_input = [i in input_args for i in arg_aliases]
if any(arg_in_input):
input_args.remove(arg_aliases[arg_in_input.index(True)])
parameters[arg_index] = True
if len(input_args) != 0 or (not parameters[2] and parameters[3]):
valid_arguments = False
show_anything = any(i for i in parameters)
if not (valid_arguments and show_anything):
await ctx.send(self.long_strings["Invalid parameters"])
else:
message_text, all_downloaded = await self.__generate_download_list(
*parameters
)
if not message_text.startswith("```"):
await ctx.send(message_text)
elif len(message_text) > 2000:
message_text = message_text[3:-3]
await send_long_message(ctx,message_text)
elif all_downloaded:
await ctx.send(message_text)
else:
updates_left = 60
message_text = self.long_strings["Update"].format(
message_text[:-3], ceil(updates_left/6)
)
old_message = await ctx.send(message_text)
while ((not all_downloaded) and updates_left > 0):
await asyncio.sleep(10)
updates_left -= 1
message_text, all_downloaded = await (
self.__generate_download_list(*parameters)
)
message_text = self.long_strings["Update"].format(
message_text[:-3],
ceil(updates_left/6)
)
await old_message.edit(content = message_text)
message_text, all_downloaded = await (
self.__generate_download_list(*parameters)
)
if message_text.startswith("```"):
if all_downloaded:
self.bot.log("All torrents are downloaded")
else:
message_text = self.long_strings["No updates"].format(
message_text[:-3]
)
self.bot.log("The message updated 20 times")
await old_message.edit(content = message_text)

View File

@ -2,4 +2,4 @@
__all__ = ["StarWars"]
from .starWars import StarWars
from .star_wars import StarWars

View File

@ -1,6 +1,6 @@
from .starWarsChar import StarWarsChar
from .starWarsRoll import StarWarsRoll
from .starWarsDestiny import StarWarsDestiny
from .star_wars_char import StarWarsChar
from .star_wars_roll import StarWarsRoll
from .star_wars_destiny import StarWarsDestiny
class StarWars():
def __init__(self, bot):

View File

@ -6,16 +6,16 @@ class StarWarsChar():
def __init__(self, bot):
self.bot = bot
def getCharName(self, user : str):
self.bot.log("Getting name for "+self.bot.databaseFuncs.getName(user)+"'s character")
def getChar_name(self, user : str):
self.bot.log("Getting name for "+self.bot.database_funcs.get_name(user)+"'s character")
userCharacter = self.bot.database["starwars characters"].find_one({"_id":user})
if userCharacter != None:
self.bot.log("Name is "+userCharacter["Name"])
return userCharacter["Name"]
else:
self.bot.log("Just using "+self.bot.databaseFuncs.getName(user))
return self.bot.databaseFuncs.getName(user)
self.bot.log("Just using "+self.bot.database_funcs.get_name(user))
return self.bot.database_funcs.get_name(user)
def setUpDict(self, cmd : dict):
self.bot.log("Setting up a dictionary in a nice way")
@ -239,7 +239,7 @@ class StarWarsChar():
return name, text1+text2+"\n\n"+text3+divider+text4+"\n"+divider+text5+text6+text7+text8
def charData(self,user : str,cmd : str):
def char_data(self,user : str,cmd : str):
userCharacter = self.bot.database["starwars characters"].find_one({"_id":user})
key = string.capwords(cmd.split(" ")[0])
@ -252,7 +252,7 @@ class StarWarsChar():
if cmd == "":
break
self.bot.log("Looking for "+self.bot.databaseFuncs.getName(user)+"'s character")
self.bot.log("Looking for "+self.bot.database_funcs.get_name(user)+"'s character")
if userCharacter != None:
self.bot.log("Found it! Looking for "+key+" in the data")
if key in userCharacter:
@ -303,7 +303,7 @@ class StarWarsChar():
return cmd[0]+" added to "+key+" for " + userCharacter["Name"]
elif key == "Weapons":
with open("resources/starWars/starwarstemplates.json", "r") as f:
with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as f:
templates = json.load(f)
newWeapon = templates["Weapon"]
self.bot.log("Adding "+cmd+" to "+key)
@ -495,20 +495,20 @@ class StarWarsChar():
text = self.replaceWithSpaces(text)
returnEmbed = True
else:
self.bot.log("Makin' a character for "+self.bot.databaseFuncs.getName(user))
with open("resources/starWars/starwarstemplates.json", "r") as f:
self.bot.log("Makin' a character for "+self.bot.database_funcs.get_name(user))
with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as f:
templates = json.load(f)
newChar = templates["Character"]
newChar["_id"] = user
self.bot.database["starwars characters"].insert_one(newChar)
await ctx.send("Character for " + self.bot.databaseFuncs.getName(user) + " created")
await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " created")
else:
if cmd == "Purge":
self.bot.log("Deleting "+self.bot.databaseFuncs.getName(user)+"'s character")
self.bot.log("Deleting "+self.bot.database_funcs.get_name(user)+"'s character")
self.bot.database["starwars characters"].delete_one({"_id":user})
await ctx.send("Character for " + self.bot.databaseFuncs.getName(user) + " deleted")
await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " deleted")
else:
await ctx.send(self.replaceWithSpaces(str(self.charData(user,cmd))))
await ctx.send(self.replaceWithSpaces(str(self.char_data(user,cmd))))
if returnEmbed:
em = discord.Embed(title = title, description = text, colour=0xDEADBF)

View File

@ -4,16 +4,16 @@ class StarWarsDestiny():
def destinyNew(self, num : int):
self.bot.log("Creating a new destiny pool with "+str(num)+" players")
roll, diceResults = self.bot.starWars.roll.roll(0,0,0,0,0,0,num)
roll, diceResults = self.bot.star_wars.roll.roll(0,0,0,0,0,0,num)
roll = "".join(sorted(roll))
with open("resources/starWars/destinyPoints.txt","wt") as f:
with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as f:
f.write(roll)
return "Rolled for Destiny Points and got:\n"+self.bot.starWars.roll.diceResultToEmoji(diceResults)+"\n"+self.bot.starWars.roll.resultToEmoji(roll)
return "Rolled for Destiny Points and got:\n"+self.bot.star_wars.roll.diceResultToEmoji(diceResults)+"\n"+self.bot.star_wars.roll.resultToEmoji(roll)
def destinyUse(self, user : str):
with open("resources/starWars/destinyPoints.txt","rt") as f:
with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f:
points = f.read()
if user == "Nikolaj":
@ -21,10 +21,10 @@ class StarWarsDestiny():
if 'B' in points:
points = points.replace("B","L",1)
points = "".join(sorted(points))
with open("resources/starWars/destinyPoints.txt","wt") as f:
with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as f:
f.write(points)
self.bot.log("Did it")
return "Used a dark side destiny point. Destiny pool is now:\n"+self.bot.starWars.roll.resultToEmoji(points)
return "Used a dark side destiny point. Destiny pool is now:\n"+self.bot.star_wars.roll.resultToEmoji(points)
else:
self.bot.log("There were no dark side destiny points")
return "No dark side destiny points"
@ -33,10 +33,10 @@ class StarWarsDestiny():
if 'L' in points:
points = points.replace("L","B",1)
points = "".join(sorted(points))
with open("resources/starWars/destinyPoints.txt","wt") as f:
with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as f:
f.write(points)
self.bot.log("Did it")
return "Used a light side destiny point. Destiny pool is now:\n"+self.bot.starWars.roll.resultToEmoji(points)
return "Used a light side destiny point. Destiny pool is now:\n"+self.bot.star_wars.roll.resultToEmoji(points)
else:
self.bot.log("There were no dark side destiny points")
return "No light side destiny points"
@ -51,22 +51,22 @@ class StarWarsDestiny():
if cmd == "":
self.bot.log("Retrieving destiny pool info")
with open("resources/starWars/destinyPoints.txt","rt") as f:
sendMessage = self.bot.starWars.roll.resultToEmoji(f.read())
with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f:
send_message = self.bot.star_wars.roll.resultToEmoji(f.read())
else:
commands = cmd.upper().split(" ")
if commands[0] == "N":
if len(commands) > 1:
sendMessage = self.destinyNew(int(commands[1]))
send_message = self.destinyNew(int(commands[1]))
else:
sendMessage = "You need to give an amount of players (error code 921)"
send_message = "You need to give an amount of players (error code 921)"
elif commands[0] == "U":
sendMessage = self.destinyUse(user)
send_message = self.destinyUse(user)
else:
sendMessage = "I didn't quite understand that (error code 922)"
send_message = "I didn't quite understand that (error code 922)"
messageList = sendMessage.split("\n")
await ctx.send(messageList[0])
if len(messageList) > 1:
for messageItem in messageList[1:]:
message_list = send_message.split("\n")
await ctx.send(message_list[0])
if len(message_list) > 1:
for messageItem in message_list[1:]:
await ctx.channel.send(messageItem)

View File

@ -3,8 +3,8 @@ import re
import string
import json
with open("resources/starWars/starwarsskills.json", "r") as f:
skillData = json.load(f)
with open("gwendolyn/resources/star_wars/starwarsskills.json", "r") as f:
skill_data = json.load(f)
class StarWarsRoll():
def __init__(self, bot):
@ -288,12 +288,12 @@ class StarWarsRoll():
characteristic = random.choice(["brawn"] * 3 + ["agility"] * 3 + ["intellect", "cunning", "presence"])
results = "**Gruesome Injury**: The target's "+characteristic+" is permanently one lower, "+dd+dd+dd+dd
sendMessage = "Roll: "+str(roll)+"\nInjury:\n"+results
send_message = "Roll: "+str(roll)+"\nInjury:\n"+results
messageList = sendMessage.split("\n")
await ctx.send(messageList[0])
if len(messageList) > 1:
for messageItem in messageList[1:]:
message_list = send_message.split("\n")
await ctx.send(message_list[0])
if len(message_list) > 1:
for messageItem in message_list[1:]:
await ctx.channel.send(messageItem)
# Parses the command into something the other functions understand
@ -302,7 +302,7 @@ class StarWarsRoll():
cmd = re.sub(' +',' ',cmd.upper()) + " "
if cmd[0] == " ":
cmd = cmd[1:]
cmd = self.bot.starWars.character.replaceSpaces(string.capwords(cmd))
cmd = self.bot.star_wars.character.replaceSpaces(string.capwords(cmd))
commands = cmd.split(" ")
validCommand = False
@ -312,19 +312,19 @@ class StarWarsRoll():
rollParameters = [0,0,0,0,0,0,0]
if string.capwords(commands[0]) == "Obligations":
sendMessage = self.obligationRoll()
send_message = self.obligationRoll()
elif string.capwords(commands[0]) in skillData:
elif string.capwords(commands[0]) in skill_data:
self.bot.log("Oh look! This guy has skills!")
if self.bot.starWars.character.userHasChar(user):
if self.bot.star_wars.character.userHasChar(user):
self.bot.log("They have a character. That much we know")
skillLevel = self.bot.starWars.character.charData(user,"Skills " + string.capwords(commands[0]))
skillLevel = self.bot.star_wars.character.char_data(user,"Skills " + string.capwords(commands[0]))
if string.capwords(commands[0]) == "Lightsaber":
self.bot.log("The skill is lightsaber")
charLevel = self.bot.starWars.character.charData(user,"Characteristics " + self.bot.starWars.character.lightsaberChar(user))
charLevel = self.bot.star_wars.character.char_data(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user))
else:
charLevel = self.bot.starWars.character.charData(user,"Characteristics " + skillData[string.capwords(commands[0])])
charLevel = self.bot.star_wars.character.char_data(user,"Characteristics " + skill_data[string.capwords(commands[0])])
abilityDice = abs(charLevel-skillLevel)
proficiencyDice = min(skillLevel,charLevel)
@ -334,14 +334,14 @@ class StarWarsRoll():
validCommand = True
else:
self.bot.log("Okay, no they don't i guess")
sendMessage = "You don't have a user. You can make one with /starwarscharacter"
send_message = "You don't have a user. You can make one with /starwarscharacter"
elif string.capwords(commands[0]) in ["Ranged","Piloting"]:
self.bot.log("They fucked up writing the name of a ranged or piloting skill")
if string.capwords(commands[0]) == "Ranged":
sendMessage = "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\" (error code 913)"
send_message = "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\" (error code 913)"
else:
sendMessage = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\" (error code 913)"
send_message = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\" (error code 913)"
else:
validCommand = True
@ -372,19 +372,19 @@ class StarWarsRoll():
simplified = self.simplify(rollResults)
name = self.bot.starWars.character.getCharName(user)
name = self.bot.star_wars.character.getChar_name(user)
self.bot.log("Returns results and simplified results")
if simplified == "":
sendMessage = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\nEverything cancels out!"
send_message = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\nEverything cancels out!"
else:
sendMessage = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\n" + self.resultToEmoji(simplified)
send_message = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\n" + self.resultToEmoji(simplified)
messageList = sendMessage.split("\n")
await ctx.send(messageList[0])
if len(messageList) > 1:
for messageItem in messageList[1:]:
message_list = send_message.split("\n")
await ctx.send(message_list[0])
if len(message_list) > 1:
for messageItem in message_list[1:]:
if messageItem == "":
self.bot.log("Tried to send empty message")
else:

View File

@ -0,0 +1,135 @@
"""
Contains the Gwendolyn class, a subclass of the discord command bot.
*Classes*
---------
Gwendolyn(discord.ext.commands.Bot)
"""
import os # Used for loading cogs in Gwendolyn.addCogs
import finnhub # Used to add a finhub client to the bot
import discord # Used for discord.Intents and discord.Status
import discord_slash # Used to initialized SlashCommands object
import git # Used to pull when stopping
from discord.ext import commands # Used to inherit from commands.bot
from pymongo import MongoClient # Used for database management
from gwendolyn.funcs import Money, StarWars, Games, Other, LookupFuncs
from gwendolyn.utils import (get_options, get_credentials, log_this,
DatabaseFuncs, EventHandler, ErrorHandler,
long_strings)
class Gwendolyn(commands.Bot):
"""
A multifunctional Discord bot.
*Methods*
---------
log(messages: Union[str, list], channel: str = "",
level: int = 20)
stop(ctx: discord_slash.context.SlashContext)
defer(ctx: discord_slash.context.SlashContext)
"""
# pylint: disable=too-many-instance-attributes
def __init__(self):
"""Initialize the bot."""
intents = discord.Intents.default()
intents.members = True
initiation_parameters = {
"command_prefix": " ",
"case_insensitive": True,
"intents": intents,
"status": discord.Status.dnd
}
super().__init__(**initiation_parameters)
self._add_clients_and_options()
self._add_util_classes()
self._add_function_containers()
self._add_cogs()
def _add_clients_and_options(self):
"""Add all the client, option and credentials objects."""
self.long_strings = long_strings()
self.options = get_options()
self.credentials = get_credentials()
finnhub_key = self.credentials["finnhub_key"]
self.finnhub_client = finnhub.Client(api_key=finnhub_key)
mongo_user = self.credentials["mongo_db_user"]
mongo_password = self.credentials["mongo_db_password"]
mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn"
mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority"
database_clint = MongoClient(mongo_url)
if self.options["testing"]:
self.log("Testing mode")
self.database = database_clint["Gwendolyn-Test"]
else:
self.database = database_clint["Gwendolyn"]
def _add_util_classes(self):
"""Add all the classes used as utility."""
self.database_funcs = DatabaseFuncs(self)
self.event_handler = EventHandler(self)
self.error_handler = ErrorHandler(self)
slash_parameters = {
"sync_commands": True,
"sync_on_cog_reload": True,
"override_type": True
}
self.slash = discord_slash.SlashCommand(self, **slash_parameters)
def _add_function_containers(self):
"""Add all the function containers used for commands."""
self.star_wars = StarWars(self)
self.other = Other(self)
self.lookup_funcs = LookupFuncs(self)
self.games = Games(self)
self.money = Money(self)
def _add_cogs(self):
"""Load cogs."""
for filename in os.listdir("./gwendolyn/cogs"):
if filename.endswith(".py"):
self.load_extension(f"gwendolyn.cogs.{filename[:-3]}")
def log(self, messages, channel: str = "", level: int = 20):
"""Log a message. Described in utils/util_functions.py."""
log_this(messages, channel, level)
async def stop(self, ctx: discord_slash.context.SlashContext):
"""
Stop the bot, and stop running games.
Only stops the bot if the user in ctx.author is one of the
admins given in options.txt.
*parameters*
------------
ctx: discord_slash.context.SlashContext
The context of the "/stop" slash command.
"""
if f"#{ctx.author.id}" in self.options["admins"]:
await ctx.send("Pulling git repo and restarting...")
await self.change_presence(status=discord.Status.offline)
self.database_funcs.wipe_games()
if not self.options["testing"]:
git_client = git.cmd.Git("")
git_client.pull()
self.log("Logging out", level=25)
await self.close()
else:
log_message = f"{ctx.author.display_name} tried to stop me!"
self.log(log_message, str(ctx.channel_id))
await ctx.send(f"I don't think I will, {ctx.author.display_name}")
async def defer(self, ctx: discord_slash.context.SlashContext):
"""Send a "Gwendolyn is thinking" message to the user."""
try:
await ctx.defer()
except discord_slash.error.AlreadyResponded:
self.log("defer failed")

View File

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 529 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

View File

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

View File

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

View File

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

View File

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

View File

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

View File

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 179 KiB

View File

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 183 KiB

View File

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

View File

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Some files were not shown because too many files have changed in this diff Show More