1
0
Fork 0
mirror of synced 2024-06-23 08:40:41 +12:00
Bonfire/cogs/hangman.py

187 lines
8.3 KiB
Python

from discord.ext import commands
from discord.ext.commands.cooldowns import BucketType
from .utils import checks
import re
class Game:
def __init__(self, word, creator):
self.word = word
self.creator = creator
# This converts everything but spaces to a blank
# TODO: Only convert [a-zA-Z0-9]
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:
def __init__(self, bot):
self.bot = bot
self.games = {}
def create(self, word, ctx):
# Create a new game, then save it as the server's game
game = Game(word, ctx.message.author)
self.games[ctx.message.server.id] = game
return game
@commands.group(aliases=['hm'], pass_context=True, no_pm=True, invoke_without_command=True)
@commands.cooldown(1, 30, BucketType.user)
@checks.custom_perms(send_messages=True)
async def hangman(self, ctx, *, guess):
"""Makes a guess towards the server's currently running hangman game"""
game = self.games.get(ctx.message.server.id)
if not game:
await self.bot.say("There are currently no hangman games running!")
return
# This check is here in case we failed in creating the hangman game
# It is easier to make this check here, instead of throwing try except's around the creation
# As there are multiple places we can fail...if we can't send a message/PM a user
if game == "placeholder":
self.games[ctx.ctx.message.server.id] = None
await self.bot.say("Sorry but the attempt to setup a game on this server failed!\n"
"This is most likely due to me not being able to PM the user who created the game\n"
"Make sure you allow this feature if you want to be able to create a hangman game")
return
if ctx.message.author == game.creator:
await self.bot.say("You can't guess at your own hangman game! :S")
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 in game.guessed_letters:
await self.bot.say("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 word was `{}`".format(game.word)
del self.games[ctx.message.server.id]
elif game.failed():
fmt += " Sorry, you guys failed...the word was `{}`".format(game.word)
del self.games[ctx.message.server.id]
else:
fmt += str(game)
await self.bot.say(fmt)
@hangman.command(name='create', aliases=['start'], no_pm=True, pass_context=True)
@checks.custom_perms(send_messages=True)
async def create_hangman(self, ctx):
"""This is used to create a new hangman game
Due to the fact that I might not be able to delete a message, I will PM you and ask for the phrase you want.
The phrase needs to be under 30 characters"""
# 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.server.id) is not None:
await self.bot.say("Sorry but only one Hangman game can be running per server!")
return
# Make sure the phrase is less than 30 characters
check = lambda m: len(m.content) < 30
# Doing this so that while we wait for the phrase, another one cannot be started.
self.games[ctx.message.server.id] = "placeholder"
# We want to send this message instead of just PM'ing the creator
# As some people have PM's turned off/ don't pay attention to them
await self.bot.say(
"I have just PM'd you {}, please respond there with the phrase you want to start a new"
" hangman game with".format(ctx.message.author.display_name))
# The only reason we save this variable, is so that we can retrieve
# The PrivateChannel for it, for use in our wait_for_message command
_msg = await self.bot.whisper("Please respond with the phrase you would like to use for your new hangman game\n"
"Please note that it must be under 30 characters long")
msg = await self.bot.wait_for_message(timeout=60.0, channel=_msg.channel, check=check)
if not msg:
await self.bot.whisper("Sorry, you took too long.")
del self.games[ctx.message.server.id]
return
else:
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 self.bot.say(
"Alright, a hangman game has just started, you can start guessing now!\n{}".format(str(game)))
@hangman.command(name='delete', aliases=['stop', 'remove', 'end'], pass_context=True, no_pm=True)
@checks.custom_perms(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"""
if self.games.get(ctx.message.server.id) is None:
await self.bot.say("There are no Hangman games running on this server!")
return
del self.games[ctx.message.server.id]
await self.bot.say("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))