import discord import os import asyncio from core.bot import bot from discord.ext import commands from typing import Dict, Set, Optional from datetime import datetime, timedelta # Lazy load repository để tránh lỗi database connection noi_tu_repo = None def get_noi_tu_repo(): """Lazy load repository""" global noi_tu_repo if noi_tu_repo is None: from repositories.noi_tu import NoiTuRepository noi_tu_repo = NoiTuRepository() return noi_tu_repo # Lấy channel ID từ environment CHANNEL_NOI_TU_ID = int(os.getenv('CHANNEL_NOI_TU_ID', 0)) # Game state class NoiTuGame: def __init__(self): self.is_active = False self.current_word = "" self.used_words: Set[str] = set() self.last_player_id = None self.last_player_name = None # Thêm tên người chơi cuối self.last_message_time = None self.timeout_task = None self.channel = None self.timer_message = None # Tin nhắn hiển thị thời gian self.timer_task = None # Task cập nhật thời gian self.start_time = None # Thời gian bắt đầu game # Khởi tạo game state game = NoiTuGame() def is_admin(ctx): """Kiểm tra xem user có phải là admin không""" return ctx.author.guild_permissions.administrator def is_correct_channel(ctx): """Kiểm tra xem command có được thực hiện trong đúng channel không""" return ctx.channel.id == CHANNEL_NOI_TU_ID def get_first_word(word: str) -> str: return word.strip().split()[0] if word else '' def get_last_word(word: str) -> str: return word.strip().split()[-1] if word else '' def format_time_remaining(seconds: int) -> str: """Format thời gian còn lại""" if seconds <= 0: return "⏰ Hết thời gian!" return f"⏰ Còn lại: {seconds} giây" async def update_timer_message(): """Cập nhật tin nhắn thời gian mỗi 1 giây""" start_time = game.last_message_time if not start_time: return for remaining in range(30, -1, -1): # Đếm ngược từ 30 đến 0 if not game.is_active or not game.timer_message: break try: # Cập nhật embed embed = game.timer_message.embeds[0] embed.title = format_time_remaining(remaining) # Thay đổi màu sắc theo thời gian if remaining <= 5: embed.color = discord.Color.red() elif remaining <= 10: embed.color = discord.Color.orange() elif remaining <= 20: embed.color = discord.Color.yellow() else: embed.color = discord.Color.blue() await game.timer_message.edit(embed=embed) # Dừng nếu hết thời gian if remaining <= 0: break await asyncio.sleep(1) # Đợi đúng 1 giây except Exception as e: print(f"Error updating timer: {e}") break @bot.command(name='start') async def start_game(ctx): """Bắt đầu trò chơi nối từ""" if not is_correct_channel(ctx): return if game.is_active: await ctx.send("❌ Trò chơi đã đang diễn ra!") return # Lấy repository repo = get_noi_tu_repo() # Lấy từ ngẫu nhiên để bắt đầu start_word = await repo.get_random_word() if not start_word: await ctx.send("❌ Không có từ nào trong cơ sở dữ liệu!") return # Khởi tạo game game.is_active = True game.current_word = start_word game.used_words = {start_word} game.last_player_id = None game.last_player_name = None game.channel = ctx.channel game.last_message_time = datetime.now() game.start_time = datetime.now() # Tạo timeout task game.timeout_task = asyncio.create_task(game_timeout()) embed = discord.Embed( title="🎮 Trò chơi Nối Từ đã bắt đầu!", description=f"Từ đầu tiên: **{start_word}**\n\n" f"📝 **Luật chơi:**\n" f"• Mỗi từ gồm 2 từ ghép tiếng Việt (VD: 'âm cao', 'cao độ')\n" f"• Từ đầu của từ mới phải trùng với từ cuối của từ trước\n" f"• Không được lặp lại từ đã dùng\n" f"• Thời gian trả lời tối đa: 30 giây\n\n" f"⏰ Thời gian bắt đầu: {datetime.now().strftime('%H:%M:%S')}", color=discord.Color.green() ) await ctx.send(embed=embed) @bot.command(name='end') async def end_game(ctx): """Kết thúc trò chơi nối từ""" if not is_correct_channel(ctx): return if not game.is_active: await ctx.send("❌ Không có trò chơi nào đang diễn ra!") return # Dừng các task if game.timeout_task: game.timeout_task.cancel() if game.timer_task: game.timer_task.cancel() # Tính thời gian chơi game_duration = "" if game.start_time: duration = datetime.now() - game.start_time minutes = int(duration.total_seconds() // 60) seconds = int(duration.total_seconds() % 60) game_duration = f"{minutes} phút {seconds} giây" # Tạo thông báo kết thúc embed = discord.Embed( title="🏁 Trò chơi Nối Từ đã kết thúc!", description=f"📊 **Thống kê:**\n" f"• Số từ đã sử dụng: {len(game.used_words)}\n" f"• Từ cuối cùng: {game.current_word if game.current_word else 'N/A'}\n" f"• Thời gian chơi: {game_duration}", color=discord.Color.red() ) # Thêm thông tin người chiến thắng if game.last_player_name: embed.add_field( name="👑 Người chiến thắng", value=f"**{game.last_player_name}** - Từ cuối: **{game.current_word}**", inline=False ) await ctx.send(embed=embed) # Reset game state game.is_active = False game.current_word = "" game.used_words.clear() game.last_player_id = None game.last_player_name = None game.channel = None game.last_message_time = None game.timeout_task = None game.timer_message = None game.timer_task = None game.start_time = None @bot.command(name='add') async def add_word(ctx, *, word: str, meaning: str = None): """Thêm từ mới vào cơ sở dữ liệu (chỉ admin)""" if not is_correct_channel(ctx): return if not is_admin(ctx): await ctx.send("❌ Chỉ admin mới có thể thêm từ!") return # Kiểm tra từ có hợp lệ không if not await get_noi_tu_repo().is_valid_word(word): await ctx.send("❌ Từ phải có đúng 2 từ ghép!") return # Kiểm tra từ đã tồn tại chưa if await get_noi_tu_repo().is_exist(word): await ctx.send(f"❌ Từ '{word}' đã tồn tại trong cơ sở dữ liệu!") return # Thêm từ success = await get_noi_tu_repo().add(word, meaning) if success: embed = discord.Embed( title="✅ Thêm từ thành công!", description=f"Từ: **{word}**\n" f"Nghĩa: {meaning if meaning else 'Không có'}", color=discord.Color.green() ) await ctx.send(embed=embed) else: await ctx.send("❌ Có lỗi xảy ra khi thêm từ!") @bot.command(name='remove') async def remove_word(ctx, *, word: str): """Xóa từ khỏi cơ sở dữ liệu (chỉ admin)""" if not is_correct_channel(ctx): return if not is_admin(ctx): await ctx.send("❌ Chỉ admin mới có thể xóa từ!") return # Kiểm tra từ có tồn tại không if not await get_noi_tu_repo().is_exist(word): await ctx.send(f"❌ Từ '{word}' không tồn tại trong cơ sở dữ liệu!") return # Xóa từ success = await get_noi_tu_repo().remove(word) if success: embed = discord.Embed( title="✅ Xóa từ thành công!", description=f"Đã xóa từ: **{word}**", color=discord.Color.green() ) await ctx.send(embed=embed) else: await ctx.send("❌ Có lỗi xảy ra khi xóa từ!") async def game_timeout(): """Xử lý timeout của game""" try: # Đợi đúng 30 giây for i in range(30): if not game.is_active: return await asyncio.sleep(1) # Kiểm tra lại trước khi timeout if game.is_active and game.last_message_time: time_diff = datetime.now() - game.last_message_time if time_diff.total_seconds() >= 30: # Game timeout embed = discord.Embed( title="⏰ Hết thời gian!", description=f"Không ai trả lời trong 30 giây.\n" f"Từ cuối cùng: **{game.current_word}**\n" f"Trò chơi kết thúc!", color=discord.Color.orange() ) # Thêm thông tin người chiến thắng if game.last_player_name: embed.add_field( name="👑 Người chiến thắng", value=f"**{game.last_player_name}** - Từ cuối: **{game.current_word}**", inline=False ) if game.channel: await game.channel.send(embed=embed) # Reset game game.is_active = False game.current_word = "" game.used_words.clear() game.last_player_id = None game.last_player_name = None game.channel = None game.last_message_time = None game.timeout_task = None game.timer_message = None game.timer_task = None game.start_time = None except asyncio.CancelledError: pass @bot.listen('on_message') async def handle_game_message(message): """Xử lý tin nhắn trong game""" # Bỏ qua tin nhắn từ bot if message.author.bot: return # Chỉ xử lý trong channel được chỉ định if message.channel.id != CHANNEL_NOI_TU_ID: return # Nếu game không active, bỏ qua if not game.is_active: return # Kiểm tra xem tin nhắn có phải là từ không word = message.content.strip().lower() # Kiểm tra từ có hợp lệ không if not await get_noi_tu_repo().is_valid_word(word): return # Kiểm tra người vừa trả lời có trả lời tiếp không if game.last_player_id == message.author.id: await message.add_reaction('❌') await message.channel.send(f"❌ **{message.author.display_name}**, hãy để người khác trả lời!") return # Kiểm tra từ có tồn tại trong DB không if not await get_noi_tu_repo().is_exist(word): await message.add_reaction('❌') return # Kiểm tra từ đã được sử dụng chưa if word in game.used_words: await message.add_reaction('❌') await message.channel.send(f"❌ Từ '{word}' đã được sử dụng!") return # Kiểm tra quy tắc nối từ ghép if game.current_word: last = get_last_word(game.current_word) first = get_first_word(word) if first != last: await message.add_reaction('❌') await message.channel.send(f"❌ Từ mới phải bắt đầu bằng từ: '{last.upper()}'!") return # Từ hợp lệ await message.add_reaction('✅') # Cập nhật game state game.current_word = word game.used_words.add(word) game.last_player_id = message.author.id game.last_player_name = message.author.display_name # Lưu tên người chơi game.last_message_time = datetime.now() # Reset timeout if game.timeout_task: game.timeout_task.cancel() game.timeout_task = asyncio.create_task(game_timeout()) # Dừng timer task cũ nếu có if game.timer_task: game.timer_task.cancel() # Thông báo từ tiếp theo next_hint = get_last_word(word).upper() embed = discord.Embed( title="⏰ Còn lại: 30 giây", color=discord.Color.blue() ) # Gửi tin nhắn mới và lưu reference game.timer_message = await message.channel.send(embed=embed) # Bắt đầu task cập nhật thời gian game.timer_task = asyncio.create_task(update_timer_message())