280 lines
9.9 KiB
Python
280 lines
9.9 KiB
Python
import discord
|
|
from discord import ui
|
|
import random
|
|
import os
|
|
import asyncio
|
|
from typing import List, Dict, Optional, Set
|
|
|
|
class WordleGame:
|
|
"""Class to handle Wordle game logic"""
|
|
|
|
def __init__(self, word: str, max_attempts: int = 6):
|
|
"""
|
|
Initialize a new Wordle game
|
|
|
|
Args:
|
|
word: The target word to guess
|
|
max_attempts: Maximum number of attempts allowed (default: 6)
|
|
"""
|
|
self.word = word.lower()
|
|
self.max_attempts = max_attempts
|
|
self.attempts = 0
|
|
self.guesses = []
|
|
self.game_over = False
|
|
self.won = False
|
|
|
|
def make_guess(self, guess: str) -> List[Dict[str, str]]:
|
|
"""
|
|
Process a guess and return the result
|
|
|
|
Args:
|
|
guess: The word guessed by the player
|
|
|
|
Returns:
|
|
List of dictionaries with letter and status (correct, present, absent)
|
|
"""
|
|
if self.game_over:
|
|
return []
|
|
|
|
guess = guess.lower()
|
|
if len(guess) != len(self.word):
|
|
return []
|
|
|
|
self.attempts += 1
|
|
self.guesses.append(guess)
|
|
|
|
# Check if the guess is correct
|
|
if guess == self.word:
|
|
self.game_over = True
|
|
self.won = True
|
|
|
|
# Check if max attempts reached
|
|
if self.attempts >= self.max_attempts:
|
|
self.game_over = True
|
|
|
|
# Process the guess
|
|
result = []
|
|
word_chars = list(self.word)
|
|
|
|
# First pass: Find exact matches
|
|
for i, char in enumerate(guess):
|
|
if i < len(self.word) and char == self.word[i]:
|
|
result.append({"letter": char, "status": "correct"})
|
|
word_chars[i] = None # Mark as used
|
|
else:
|
|
result.append({"letter": char, "status": "unknown"})
|
|
|
|
# Second pass: Find misplaced letters
|
|
for i, item in enumerate(result):
|
|
if item["status"] == "unknown":
|
|
char = item["letter"]
|
|
if char in word_chars:
|
|
# Letter exists but in wrong position
|
|
result[i]["status"] = "present"
|
|
word_chars[word_chars.index(char)] = None # Mark as used
|
|
else:
|
|
# Letter doesn't exist in the word
|
|
result[i]["status"] = "absent"
|
|
|
|
return result
|
|
|
|
class WordleView(ui.View):
|
|
"""Discord UI View for the Wordle game"""
|
|
|
|
def __init__(self, player: discord.Member, word: str, timeout: float = 600.0):
|
|
"""
|
|
Initialize the Wordle game view
|
|
|
|
Args:
|
|
player: The Discord member playing the game
|
|
word: The target word to guess
|
|
timeout: Time in seconds before the view times out
|
|
"""
|
|
super().__init__(timeout=timeout)
|
|
self.player = player
|
|
self.game = WordleGame(word)
|
|
self.message: Optional[discord.Message] = None
|
|
self.used_letters: Set[str] = set()
|
|
|
|
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
|
"""Ensure only the player can interact with the game"""
|
|
if interaction.user.id != self.player.id:
|
|
await interaction.response.send_message("This is not your game!", ephemeral=True)
|
|
return False
|
|
return True
|
|
|
|
async def on_timeout(self) -> None:
|
|
"""Handle timeout - disable buttons and show the answer"""
|
|
if self.message:
|
|
self.game.game_over = True
|
|
await self.update_message(None, timeout=True)
|
|
|
|
def format_board(self) -> str:
|
|
"""Format the game board into a string representation"""
|
|
if not self.game.guesses:
|
|
# No guesses yet
|
|
return "No guesses yet. Use the button below to make a guess!"
|
|
|
|
board_lines = []
|
|
|
|
# Add each guess with colored squares
|
|
for guess in self.game.guesses:
|
|
result = self.game.make_guess(guess)
|
|
# Skip re-processing the guess, just get the result
|
|
self.game.attempts -= 1
|
|
|
|
guess_line = ""
|
|
for item in result:
|
|
if item["status"] == "correct":
|
|
guess_line += f"🟩" # Green square for correct position
|
|
elif item["status"] == "present":
|
|
guess_line += f"🟨" # Yellow square for correct letter, wrong position
|
|
else:
|
|
guess_line += f"⬛" # Black square for incorrect letter
|
|
|
|
# Track used letters
|
|
self.used_letters.add(item["letter"])
|
|
|
|
# Add the actual letters after the squares
|
|
guess_line += f" {guess.upper()}"
|
|
board_lines.append(guess_line)
|
|
|
|
return "\n".join(board_lines)
|
|
|
|
def format_keyboard(self) -> str:
|
|
"""Format a keyboard showing used letters"""
|
|
if not self.used_letters:
|
|
return ""
|
|
|
|
keyboard = [
|
|
"QWERTYUIOP",
|
|
"ASDFGHJKL",
|
|
"ZXCVBNM"
|
|
]
|
|
|
|
keyboard_lines = []
|
|
for row in keyboard:
|
|
line = ""
|
|
for letter in row:
|
|
letter_lower = letter.lower()
|
|
if letter_lower in self.used_letters:
|
|
# Check the status of this letter in the most recent guess
|
|
status = "absent" # Default to absent
|
|
for guess in self.game.guesses:
|
|
for i, char in enumerate(guess):
|
|
if char == letter_lower:
|
|
if char == self.game.word[i]:
|
|
status = "correct"
|
|
break
|
|
elif char in self.game.word:
|
|
status = "present"
|
|
|
|
if status == "correct":
|
|
line += f"🟩" # Green for correct
|
|
elif status == "present":
|
|
line += f"🟨" # Yellow for present
|
|
else:
|
|
line += f"⬛" # Black for absent
|
|
else:
|
|
line += f"⬜" # White for unused
|
|
keyboard_lines.append(line)
|
|
|
|
return "\n".join(keyboard_lines)
|
|
|
|
async def update_message(self, interaction: Optional[discord.Interaction] = None, timeout: bool = False) -> None:
|
|
"""Update the game message with the current state"""
|
|
if not self.message:
|
|
return
|
|
|
|
# Format the message content
|
|
content = f"# Wordle Game\n\n"
|
|
content += self.format_board()
|
|
content += f"\n\n"
|
|
|
|
# Add keyboard
|
|
keyboard = self.format_keyboard()
|
|
if keyboard:
|
|
content += f"{keyboard}\n\n"
|
|
|
|
# Add game status
|
|
attempts_left = self.game.max_attempts - self.game.attempts
|
|
content += f"Attempts: {self.game.attempts}/{self.game.max_attempts} ({attempts_left} left)\n"
|
|
|
|
# Add game result if game is over
|
|
if self.game.game_over:
|
|
self.clear_items() # Remove all buttons
|
|
if self.game.won:
|
|
content += f"\n🎉 You won! The word was **{self.game.word.upper()}**."
|
|
elif timeout:
|
|
content += f"\n⏰ Time's up! The word was **{self.game.word.upper()}**."
|
|
else:
|
|
content += f"\n❌ Game over! The word was **{self.game.word.upper()}**."
|
|
|
|
# Update the message
|
|
if interaction:
|
|
await interaction.response.edit_message(content=content, view=self)
|
|
else:
|
|
await self.message.edit(content=content, view=self)
|
|
|
|
@ui.button(label="Make a Guess", style=discord.ButtonStyle.primary)
|
|
async def guess_button(self, interaction: discord.Interaction, button: ui.Button):
|
|
"""Button to make a guess"""
|
|
# Create and send the modal
|
|
modal = WordleGuessModal(self)
|
|
await interaction.response.send_modal(modal)
|
|
|
|
class WordleGuessModal(ui.Modal, title="Enter your guess"):
|
|
"""Modal for entering a Wordle guess"""
|
|
|
|
guess = ui.TextInput(
|
|
label="Your 5-letter guess",
|
|
placeholder="Enter a 5-letter word",
|
|
min_length=5,
|
|
max_length=5,
|
|
required=True
|
|
)
|
|
|
|
def __init__(self, view: WordleView):
|
|
super().__init__()
|
|
self.wordle_view = view
|
|
|
|
async def on_submit(self, interaction: discord.Interaction):
|
|
"""Process the submitted guess"""
|
|
guess = self.guess.value.strip().lower()
|
|
|
|
# Validate the guess
|
|
if len(guess) != 5:
|
|
await interaction.response.send_message("Please enter a 5-letter word.", ephemeral=True)
|
|
return
|
|
|
|
if not guess.isalpha():
|
|
await interaction.response.send_message("Your guess must contain only letters.", ephemeral=True)
|
|
return
|
|
|
|
# Process the guess
|
|
self.wordle_view.game.make_guess(guess)
|
|
|
|
# Update the game message
|
|
await self.wordle_view.update_message(interaction)
|
|
|
|
def load_word_list(file_path: str = "words.txt", word_length: int = 5) -> List[str]:
|
|
"""
|
|
Load and filter words from a file
|
|
|
|
Args:
|
|
file_path: Path to the words file
|
|
word_length: Length of words to filter (default: 5 for Wordle)
|
|
|
|
Returns:
|
|
List of words with the specified length
|
|
"""
|
|
try:
|
|
with open(file_path, "r") as file:
|
|
words = [word.strip().lower() for word in file if word.strip()]
|
|
# Filter words by length
|
|
filtered_words = [word for word in words if len(word) == word_length]
|
|
return filtered_words
|
|
except FileNotFoundError:
|
|
print(f"Word list file not found: {file_path}")
|
|
return []
|