1
0
Fork 0
mirror of synced 2024-04-29 02:02:28 +12:00
Bonfire/cogs/hangman.py

232 lines
8.5 KiB
Python

from discord.ext import commands
from discord.ext.commands.cooldowns import BucketType
import discord
from utils import checks
import re
import asyncio
class Game:
def __init__(self, word):
self.word = word
# This converts everything but spaces to a blank
self.blanks = "".join(
letter if not re.search("[a-zA-Z0-9]", letter) else "_" for letter in word
)
self.failed_letters = []
self.guessed_letters = []
self.fails = 0
def guess_letter(self, letter):
# No matter what, add this to guessed letters so we only have to do one check if a letter was already guessed
self.guessed_letters.append(letter)
if letter.lower() in self.word.lower():
# Replace every occurence of the guessed letter, with the correct letter
# Use the one in the word instead of letter, due to capitalization
self.blanks = "".join(
word_letter if letter.lower() == word_letter.lower() else self.blanks[i]
for i, word_letter in enumerate(self.word)
)
return True
else:
self.fails += 1
self.failed_letters.append(letter)
return False
def guess_word(self, word):
if word.lower() == self.word.lower():
self.blanks = self.word
return True
else:
self.fails += 1
return False
def win(self):
return self.word == self.blanks
def failed(self):
return self.fails == 7
def __str__(self):
# Here's our fancy formatting for the hangman picture
# Each position in the hangman picture is either a space, or part of the man, based on how many fails there are
man = " ——\n"
man += " | |\n"
man += " {} |\n".format("o" if self.fails > 0 else " ")
man += " {}{}{} |\n".format(
"/" if self.fails > 1 else " ",
"|" if self.fails > 2 else " ",
"\\" if self.fails > 3 else " ",
)
man += " {} |\n".format("|" if self.fails > 4 else " ")
man += " {} {} |\n".format(
"/" if self.fails > 5 else " ", "\\" if self.fails > 6 else " "
)
man += " |\n"
man += " ———————\n"
fmt = "```\n{}```".format(man)
# Then just add the guesses and the blanks to the string
fmt += "```\nGuesses: {}\nWord: {}```".format(
", ".join(self.failed_letters), " ".join(self.blanks)
)
return fmt
class Hangman(commands.Cog):
"""Pretty self-explanatory"""
games = {}
pending_games = []
def create(self, word, ctx):
# Create a new game, then save it as the server's game
game = Game(word)
self.games[ctx.message.guild.id] = game
game.author = ctx.message.author.id
return game
@commands.group(aliases=["hm"], invoke_without_command=True)
@commands.guild_only()
@commands.cooldown(1, 7, BucketType.user)
@checks.can_run(send_messages=True)
async def hangman(self, ctx, *, guess):
"""Makes a guess towards the server's currently running hangman game
EXAMPLE: !hangman e (or) !hangman The Phrase!
RESULT: Hopefully a win!"""
game = self.games.get(ctx.message.guild.id)
if not game:
ctx.command.reset_cooldown(ctx)
await ctx.send("There are currently no hangman games running!")
return
if game.author == ctx.message.author.id:
await ctx.send("You cannot guess on your own hangman game!")
return
# Check if we are guessing a letter or a phrase. Only one letter can be guessed at a time
# So if anything more than one was provided, we're guessing at the phrase
# We're creating a fmt variable, so that we can add a message for if a guess was correct or not
# And also add a message for a loss/win
if len(guess) == 1:
if guess.lower() in game.guessed_letters:
ctx.command.reset_cooldown(ctx)
await ctx.send("That letter has already been guessed!")
# Return here as we don't want to count this as a failure
return
if game.guess_letter(guess):
fmt = "That's correct!"
else:
fmt = "Sorry, that letter is not in the phrase..."
else:
if game.guess_word(guess):
fmt = "That's correct!"
else:
fmt = "Sorry that's not the correct phrase..."
if game.win():
fmt += " You guys got it! The phrase was `{}`".format(game.word)
del self.games[ctx.message.guild.id]
elif game.failed():
fmt += " Sorry, you guys failed...the phrase was `{}`".format(game.word)
del self.games[ctx.message.guild.id]
else:
fmt += str(game)
await ctx.send(fmt)
@hangman.command(name="create", aliases=["start"])
@commands.guild_only()
@checks.can_run(send_messages=True)
async def create_hangman(self, ctx):
"""This is used to create a new hangman game
A predefined phrase will be randomly chosen as the phrase to use
EXAMPLE: !hangman start
RESULT: This is pretty obvious .-."""
# Only have one hangman game per server, since anyone
# In a server (except the creator) can guess towards the current game
if self.games.get(ctx.message.guild.id) is not None:
await ctx.send("Sorry but only one Hangman game can be running per server!")
return
if ctx.guild.id in self.pending_games:
await ctx.send(
"Someone has already started one, and I'm now waiting for them..."
)
return
try:
msg = await ctx.message.author.send(
"Please respond with a phrase you would like to use for your hangman game in **{}**\n\nPlease keep "
"phrases less than 31 characters".format(ctx.message.guild.name)
)
except discord.Forbidden:
await ctx.send(
"I can't message you {}! Please allow DM's so I can message you and ask for the hangman phrase you "
"want to use!".format(ctx.message.author.display_name)
)
return
await ctx.send(
"I have DM'd you {}, please respond there with the phrase you would like to setup".format(
ctx.message.author.display_name
)
)
def check(m):
return m.channel == msg.channel and len(m.content) <= 30
self.pending_games.append(ctx.guild.id)
try:
msg = await ctx.bot.wait_for("message", check=check, timeout=60)
except asyncio.TimeoutError:
self.pending_games.remove(ctx.guild.id)
await ctx.send(
"You took too long! Please look at your DM's as that's where I'm asking for the phrase you want to use"
)
return
else:
self.pending_games.remove(ctx.guild.id)
forbidden_phrases = ["stop", "delete", "remove", "end", "create", "start"]
if msg.content in forbidden_phrases:
await ctx.send(
"Detected forbidden hangman phrase; current forbidden phrases are: \n{}".format(
"\n".join(forbidden_phrases)
)
)
return
game = self.create(msg.content, ctx)
# Let them know the game has started, then print the current game so that the blanks are shown
await ctx.send(
"Alright, a hangman game has just started, you can start guessing now!\n{}".format(
str(game)
)
)
@hangman.command(name="delete", aliases=["stop", "remove", "end"])
@commands.guild_only()
@checks.can_run(kick_members=True)
async def stop_game(self, ctx):
"""Force stops a game of hangman
This should realistically only be used in a situation like one player leaves
Hopefully a moderator will not abuse it, but there's not much we can do to avoid that
EXAMPLE: !hangman stop
RESULT: No more men being hung"""
if self.games.get(ctx.message.guild.id) is None:
await ctx.send("There are no Hangman games running on this server!")
return
del self.games[ctx.message.guild.id]
await ctx.send(
"I have just stopped the game of Hangman, a new should be able to be started now!"
)
def setup(bot):
bot.add_cog(Hangman(bot))