Files
Mace-AIO-Discord-Bot---With…/bot/games/country_guess.py
TheOnlyMace b4110c3d66
Some checks failed
CI / Bot (Python) (push) Failing after 49s
CI / Dashboard (Next.js) (push) Failing after 11s
Update project configuration files, and API routes accordingly. Add SQLite runtime file ignores to .gitignore for better file management.
2026-07-21 17:11:38 +02:00

236 lines
8.2 KiB
Python

# ╔══════════════════════════════════════════════════════════════════╗
# ║ ║
# ║ +-+-+-+-+-+-+-+-+ ║
# ║ |H|e|x|a|H|o|s|t| ║
# ║ +-+-+-+-+-+-+-+-+ ║
# ║ ║
# ║ © 2026 HexaHost — All Rights Reserved ║
# ║ ║
# ║ discord ── https://discord.gg/hexahost ║
# ║ github ── https://github.com/theoneandonlymace ║
# ║ ║
# ╚══════════════════════════════════════════════════════════════════╝
import asyncio
import difflib
import os
import pathlib
import random
from typing import Union, Optional
from io import BytesIO
import discord
from discord.ext import commands
from PIL import Image, ImageFilter, ImageOps
from .utils import *
class CountryGuesser:
"""
CountryGuesser Game
"""
embed: discord.Embed
accepted_length: Optional[int]
country: str
def __init__(
self,
*,
is_flags: bool = False,
light_mode: bool = False,
hard_mode: bool = False,
hints: int = 1,
) -> None:
self.embed_color: Optional[DiscordColor] = None
self.hints = hints
self.is_flags = is_flags
self.hard_mode = hard_mode
if self.is_flags:
self.light_mode = False
else:
self.light_mode = light_mode
folder = "assets/country-flags" if self.is_flags else "assets/country-data"
self._countries_path = pathlib.Path(__file__).parent / folder
self.all_countries = os.listdir(self._countries_path)
self.responses_count = 0
@executor()
def invert_image(self, image_path: Union[BytesIO, os.PathLike, str]) -> BytesIO:
with Image.open(image_path) as img:
img = img.convert("RGBA")
r, g, b, a = img.split()
rgb = Image.merge("RGB", (r, g, b))
rgb = ImageOps.invert(rgb)
rgb = rgb.split()
img = Image.merge("RGBA", rgb + (a,))
buf = BytesIO()
img.save(buf, "PNG")
buf.seek(0)
return buf
@executor()
def blur_image(self, image_path: Union[BytesIO, os.PathLike, str]) -> BytesIO:
with Image.open(image_path) as img:
img = img.convert("RGBA")
img = img.filter(ImageFilter.GaussianBlur(10))
buf = BytesIO()
img.save(buf, "PNG")
buf.seek(0)
return buf
async def get_country(self) -> discord.File:
country_file = random.choice(self.all_countries)
self.country = country_file.strip()[:-4].lower()
file = os.path.join(self._countries_path, country_file)
if self.hard_mode:
file = await self.blur_image(file)
if self.light_mode:
file = await self.invert_image(file)
return discord.File(file, "country.png")
def get_blanks(self) -> str:
return " ".join("_" if char != " " else " " for char in self.country)
def get_hint(self) -> str:
blanks = ["_" if char != " " else " " for char in self.country]
times = round(len(blanks) / 3)
for _ in range(times):
idx = random.choice(range(len(self.country)))
blanks[idx] = self.country[idx]
return " ".join(blanks)
def get_accuracy(self, guess: str) -> int:
return round(difflib.SequenceMatcher(None, guess, self.country).ratio() * 100)
def get_embed(self) -> discord.Embed:
embed = discord.Embed(
title="Guess that country!",
description=f"```fix\n{self.get_blanks()}\n```",
color=discord.Color.random(),
)
embed.add_field(
name="\u200b",
value=f"```yml\nblurred: {str(self.hard_mode).lower()}\nflag-mode: {str(self.is_flags).lower()}\n```",
inline=False,
)
embed.set_image(url="attachment://country.png")
return embed
async def wait_for_response(
self,
ctx: commands.Context[commands.Bot],
*,
options: tuple[str, ...] = (),
length: Optional[int] = None,
) -> Optional[tuple[discord.Message, str]]:
def check(m: discord.Message) -> bool:
if length:
return (
m.channel == ctx.channel
and m.author != ctx.bot.user
and len(m.content) == length
)
else:
return m.channel == ctx.channel and m.author != ctx.bot.user
message: discord.Message = await ctx.bot.wait_for(
"message", timeout=self.timeout, check=check
)
content = message.content.strip().lower()
if options:
if not content in options:
return
return message, content
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
timeout: Optional[float] = 100.0,
embed_color: DiscordColor = discord.Color.random(),
) -> discord.Message:
file = await self.get_country()
self.timeout = timeout
self.embed_color = discord.Color.random()
self.embed = self.get_embed()
self.embed.set_footer(text="send your guess within 100 seconds into the chat now!")
self.message = await ctx.send(embed=self.embed, file=file)
self.accepted_length = None
start_time = asyncio.get_event_loop().time()
while not ctx.bot.is_closed() and asyncio.get_event_loop().time() - start_time < self.timeout:
try:
msg, response = await self.wait_for_response(ctx)
except asyncio.TimeoutError:
break
self.responses_count += 1
if response == self.country:
elapsed_time = round(asyncio.get_event_loop().time() - start_time, 2)
await msg.reply(
f"That is correct! The country was `{self.country.title()}`"
)
return await self.end_game(ctx, msg.author, elapsed_time)
else:
acc = self.get_accuracy(response)
if self.responses_count % 10 == 0 and self.hints:
hint = self.get_hint()
await ctx.send(f"Hint: `{hint}`")
await msg.reply(
f"That was incorrect! but you are `{acc}%` of the way there!",
mention_author=False,
)
# Check if the time has exceeded the timeout
#if asyncio.get_event_loop().time() - start_time > timeout:
#return await self.end_game(ctx) # Call end_game when timeout occurs
return await self.end_game(ctx)
async def end_game(
self,
ctx: commands.Context[commands.Bot],
winner: Optional[discord.User] = None,
time_taken: Optional[float] = None, manual_end: bool = False
) -> discord.Message:
embed = discord.Embed(title="Game Over", color=self.embed_color)
if winner and time_taken:
embed.add_field(
name="Winner",
value=f"{winner.mention} ({winner.name})",
inline=False,
)
embed.add_field(name="Time Taken", value=f"{time_taken} seconds", inline=False)
elif manual_end:
embed.description = "The game was manually ended."
else:
embed.description = f"Time's up! No one guessed the country. The correct answer was `{self.country.title()}`."
return await ctx.send(embed=embed)
async def end_game_manually(self, ctx: commands.Context):
await self.end_game(ctx, manual_end=True)