Skip to content

Hangman Game with Database Integration

Abstract

Create a comprehensive hangman game with database integration that includes user authentication, statistics tracking, word management, and advanced gameplay features. This project demonstrates database operations, user session management, game state persistence, and building feature-rich gaming applications.

Prerequisites

  • Python 3.7 or above
  • Text Editor or IDE
  • Solid understanding of Python syntax and OOP concepts
  • Knowledge of SQLite database operations
  • Familiarity with GUI development using Tkinter
  • Understanding of user authentication and session management
  • Basic knowledge of game development principles

Getting Started

Create a new project

  1. Create a new project folder and name it hangmanDatabasehangmanDatabase.
  2. Create a new file and name it hangmandatabase.pyhangmandatabase.py.
  3. Install required dependencies: pip install tkinter sqlite3pip install tkinter sqlite3
  4. Open the project folder in your favorite text editor or IDE.
  5. Copy the code below and paste it into your hangmandatabase.pyhangmandatabase.py file.

Write the code

  1. Add the following code to your hangmandatabase.pyhangmandatabase.py file.
⚙️ Hangman Game with Database Integration
Hangman Game with Database Integration
# Advanced Hangman Game with Database
 
import sqlite3
import random
import json
import hashlib
import datetime
from typing import List, Dict, Optional, Tuple
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import requests
from pathlib import Path
import re
import string
 
class HangmanDatabase:
    """Database manager for hangman game"""
    
    def __init__(self, db_path: str = "hangman.db"):
        self.db_path = db_path
        self.init_database()
    
    def init_database(self):
        """Initialize database tables"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            
            # Words table
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS words (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    word TEXT UNIQUE NOT NULL,
                    category TEXT NOT NULL,
                    difficulty INTEGER NOT NULL,
                    hint TEXT,
                    added_date TEXT NOT NULL,
                    used_count INTEGER DEFAULT 0
                )
            """)
            
            # Players table
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS players (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    username TEXT UNIQUE NOT NULL,
                    password_hash TEXT NOT NULL,
                    created_date TEXT NOT NULL,
                    last_login TEXT,
                    total_games INTEGER DEFAULT 0,
                    total_wins INTEGER DEFAULT 0,
                    total_losses INTEGER DEFAULT 0,
                    best_streak INTEGER DEFAULT 0,
                    current_streak INTEGER DEFAULT 0
                )
            """)
            
            # Games table
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS games (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    player_id INTEGER NOT NULL,
                    word_id INTEGER NOT NULL,
                    guessed_letters TEXT NOT NULL,
                    wrong_guesses INTEGER NOT NULL,
                    max_wrong_guesses INTEGER NOT NULL,
                    won BOOLEAN NOT NULL,
                    duration_seconds INTEGER,
                    game_date TEXT NOT NULL,
                    FOREIGN KEY (player_id) REFERENCES players (id),
                    FOREIGN KEY (word_id) REFERENCES words (id)
                )
            """)
            
            # Achievements table
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS achievements (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    player_id INTEGER NOT NULL,
                    achievement_type TEXT NOT NULL,
                    achievement_data TEXT,
                    earned_date TEXT NOT NULL,
                    FOREIGN KEY (player_id) REFERENCES players (id)
                )
            """)
            
            conn.commit()
            
            # Add default words if database is empty
            self.add_default_words()
    
    def add_default_words(self):
        """Add default word list if database is empty"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT COUNT(*) FROM words")
            if cursor.fetchone()[0] == 0:
                default_words = [
                    # Animals
                    ("elephant", "animals", 2, "Large mammal with trunk"),
                    ("giraffe", "animals", 2, "Tallest animal in the world"),
                    ("penguin", "animals", 2, "Flightless bird from Antarctica"),
                    ("dolphin", "animals", 2, "Intelligent marine mammal"),
                    ("butterfly", "animals", 3, "Colorful flying insect"),
                    ("rhinoceros", "animals", 4, "Large horned mammal"),
                    ("hippopotamus", "animals", 4, "Large water-loving mammal"),
                    
                    # Countries
                    ("france", "countries", 2, "European country known for Eiffel Tower"),
                    ("japan", "countries", 1, "Island nation in East Asia"),
                    ("brazil", "countries", 2, "Largest South American country"),
                    ("australia", "countries", 3, "Island continent"),
                    ("switzerland", "countries", 4, "Neutral European country"),
                    ("madagascar", "countries", 4, "Large island off Africa"),
                    
                    # Technology
                    ("computer", "technology", 2, "Electronic device for processing data"),
                    ("internet", "technology", 2, "Global network of computers"),
                    ("smartphone", "technology", 3, "Portable communication device"),
                    ("artificial", "technology", 4, "Type of intelligence in machines"),
                    ("programming", "technology", 4, "Process of creating software"),
                    ("cybersecurity", "technology", 5, "Protection of digital systems"),
                    
                    # Science
                    ("gravity", "science", 2, "Force that pulls objects down"),
                    ("molecule", "science", 3, "Smallest unit of a compound"),
                    ("photosynthesis", "science", 5, "Process plants use to make food"),
                    ("ecosystem", "science", 3, "Community of living organisms"),
                    ("chromosome", "science", 4, "Structure containing DNA"),
                    ("quantum", "science", 3, "Related to atomic particles"),
                    
                    # Sports
                    ("basketball", "sports", 3, "Sport played with orange ball"),
                    ("swimming", "sports", 2, "Water sport activity"),
                    ("marathon", "sports", 3, "Long distance running race"),
                    ("gymnastics", "sports", 4, "Sport involving flexibility and strength"),
                    ("badminton", "sports", 3, "Racquet sport with shuttlecock"),
                    
                    # Food
                    ("chocolate", "food", 2, "Sweet treat made from cocoa"),
                    ("spaghetti", "food", 3, "Long thin pasta"),
                    ("hamburger", "food", 3, "Ground meat sandwich"),
                    ("pizza", "food", 1, "Italian flatbread with toppings"),
                    ("sandwich", "food", 2, "Food between two pieces of bread"),
                ]
                
                for word, category, difficulty, hint in default_words:
                    self.add_word(word, category, difficulty, hint)
    
    def add_word(self, word: str, category: str, difficulty: int, hint: str = ""):
        """Add a new word to database"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            try:
                cursor.execute("""
                    INSERT INTO words (word, category, difficulty, hint, added_date)
                    VALUES (?, ?, ?, ?, ?)
                """, (word.lower(), category.lower(), difficulty, hint, datetime.datetime.now().isoformat()))
                conn.commit()
                return True
            except sqlite3.IntegrityError:
                return False  # Word already exists
    
    def get_random_word(self, category: str = None, difficulty: int = None) -> Optional[Dict]:
        """Get a random word from database"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            
            query = "SELECT id, word, category, difficulty, hint FROM words WHERE 1=1"
            params = []
            
            if category:
                query += " AND category = ?"
                params.append(category.lower())
            
            if difficulty:
                query += " AND difficulty = ?"
                params.append(difficulty)
            
            cursor.execute(query, params)
            words = cursor.fetchall()
            
            if words:
                word_data = random.choice(words)
                return {
                    'id': word_data[0],
                    'word': word_data[1],
                    'category': word_data[2],
                    'difficulty': word_data[3],
                    'hint': word_data[4]
                }
            return None
    
    def get_categories(self) -> List[str]:
        """Get all available categories"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT DISTINCT category FROM words ORDER BY category")
            return [row[0] for row in cursor.fetchall()]
    
    def create_player(self, username: str, password: str) -> bool:
        """Create a new player account"""
        password_hash = hashlib.sha256(password.encode()).hexdigest()
        
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            try:
                cursor.execute("""
                    INSERT INTO players (username, password_hash, created_date)
                    VALUES (?, ?, ?)
                """, (username, password_hash, datetime.datetime.now().isoformat()))
                conn.commit()
                return True
            except sqlite3.IntegrityError:
                return False  # Username already exists
    
    def authenticate_player(self, username: str, password: str) -> Optional[Dict]:
        """Authenticate player login"""
        password_hash = hashlib.sha256(password.encode()).hexdigest()
        
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("""
                SELECT id, username, total_games, total_wins, total_losses, 
                       best_streak, current_streak
                FROM players 
                WHERE username = ? AND password_hash = ?
            """, (username, password_hash))
            
            result = cursor.fetchone()
            if result:
                # Update last login
                cursor.execute("""
                    UPDATE players SET last_login = ? WHERE id = ?
                """, (datetime.datetime.now().isoformat(), result[0]))
                conn.commit()
                
                return {
                    'id': result[0],
                    'username': result[1],
                    'total_games': result[2],
                    'total_wins': result[3],
                    'total_losses': result[4],
                    'best_streak': result[5],
                    'current_streak': result[6]
                }
            return None
    
    def save_game(self, player_id: int, word_id: int, guessed_letters: List[str], 
                  wrong_guesses: int, max_wrong_guesses: int, won: bool, duration: int):
        """Save game result to database"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            
            # Save game
            cursor.execute("""
                INSERT INTO games (player_id, word_id, guessed_letters, wrong_guesses,
                                 max_wrong_guesses, won, duration_seconds, game_date)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """, (player_id, word_id, json.dumps(guessed_letters), wrong_guesses,
                  max_wrong_guesses, won, duration, datetime.datetime.now().isoformat()))
            
            # Update player stats
            if won:
                cursor.execute("""
                    UPDATE players 
                    SET total_games = total_games + 1, 
                        total_wins = total_wins + 1,
                        current_streak = current_streak + 1
                    WHERE id = ?
                """, (player_id,))
                
                # Check for new best streak
                cursor.execute("SELECT current_streak, best_streak FROM players WHERE id = ?", (player_id,))
                current, best = cursor.fetchone()
                if current > best:
                    cursor.execute("UPDATE players SET best_streak = ? WHERE id = ?", (current, player_id))
            else:
                cursor.execute("""
                    UPDATE players 
                    SET total_games = total_games + 1, 
                        total_losses = total_losses + 1,
                        current_streak = 0
                    WHERE id = ?
                """, (player_id,))
            
            # Update word usage count
            cursor.execute("UPDATE words SET used_count = used_count + 1 WHERE id = ?", (word_id,))
            
            conn.commit()
    
    def get_player_stats(self, player_id: int) -> Dict:
        """Get detailed player statistics"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            
            # Basic stats
            cursor.execute("""
                SELECT username, total_games, total_wins, total_losses, 
                       best_streak, current_streak, created_date
                FROM players WHERE id = ?
            """, (player_id,))
            
            player_data = cursor.fetchone()
            
            # Recent games
            cursor.execute("""
                SELECT g.won, g.duration_seconds, g.game_date, w.word, w.category
                FROM games g
                JOIN words w ON g.word_id = w.id
                WHERE g.player_id = ?
                ORDER BY g.game_date DESC
                LIMIT 10
            """, (player_id,))
            
            recent_games = cursor.fetchall()
            
            # Category performance
            cursor.execute("""
                SELECT w.category, COUNT(*) as games, SUM(g.won) as wins
                FROM games g
                JOIN words w ON g.word_id = w.id
                WHERE g.player_id = ?
                GROUP BY w.category
            """, (player_id,))
            
            category_stats = cursor.fetchall()
            
            win_rate = (player_data[2] / player_data[1] * 100) if player_data[1] > 0 else 0
            
            return {
                'username': player_data[0],
                'total_games': player_data[1],
                'total_wins': player_data[2],
                'total_losses': player_data[3],
                'win_rate': win_rate,
                'best_streak': player_data[4],
                'current_streak': player_data[5],
                'member_since': player_data[6],
                'recent_games': recent_games,
                'category_stats': category_stats
            }
    
    def get_leaderboard(self, limit: int = 10) -> List[Dict]:
        """Get top players leaderboard"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("""
                SELECT username, total_games, total_wins, total_losses, 
                       best_streak, current_streak,
                       CASE WHEN total_games > 0 THEN ROUND(total_wins * 100.0 / total_games, 1) ELSE 0 END as win_rate
                FROM players
                WHERE total_games > 0
                ORDER BY total_wins DESC, win_rate DESC, best_streak DESC
                LIMIT ?
            """, (limit,))
            
            return [
                {
                    'username': row[0],
                    'total_games': row[1],
                    'total_wins': row[2],
                    'total_losses': row[3],
                    'best_streak': row[4],
                    'current_streak': row[5],
                    'win_rate': row[6]
                }
                for row in cursor.fetchall()
            ]
 
class HangmanGame:
    """Hangman game logic"""
    
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.reset_game()
        
        # Hangman drawings
        self.hangman_stages = [
            """
   +---+
   |   |
       |
       |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
       |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
   |   |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
  /|   |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
  /|\\  |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
  /|\\  |
  /    |
       |
=========
""",
            """
   +---+
   |   |
   O   |
  /|\\  |
  / \\  |
       |
=========
"""
        ]
    
    def reset_game(self):
        """Reset game state"""
        self.current_word_data = None
        self.current_word = ""
        self.guessed_letters = []
        self.wrong_guesses = 0
        self.max_wrong_guesses = 6
        self.game_won = False
        self.game_over = False
        self.start_time = None
        self.end_time = None
    
    def start_new_game(self, category: str = None, difficulty: int = None) -> bool:
        """Start a new game"""
        self.reset_game()
        
        self.current_word_data = self.db.get_random_word(category, difficulty)
        if not self.current_word_data:
            return False
        
        self.current_word = self.current_word_data['word'].upper()
        self.start_time = datetime.datetime.now()
        return True
    
    def make_guess(self, letter: str) -> Dict:
        """Make a letter guess"""
        letter = letter.upper()
        
        if self.game_over:
            return {'status': 'game_over', 'message': 'Game is already over'}
        
        if letter in self.guessed_letters:
            return {'status': 'already_guessed', 'message': f'You already guessed "{letter}"'}
        
        if not letter.isalpha() or len(letter) != 1:
            return {'status': 'invalid', 'message': 'Please enter a single letter'}
        
        self.guessed_letters.append(letter)
        
        if letter in self.current_word:
            # Correct guess
            if self.is_word_complete():
                self.game_won = True
                self.game_over = True
                self.end_time = datetime.datetime.now()
                return {
                    'status': 'won',
                    'message': f'Congratulations! You won! The word was "{self.current_word}"',
                    'correct': True
                }
            else:
                return {
                    'status': 'correct',
                    'message': f'Good guess! "{letter}" is in the word',
                    'correct': True
                }
        else:
            # Wrong guess
            self.wrong_guesses += 1
            
            if self.wrong_guesses >= self.max_wrong_guesses:
                self.game_over = True
                self.end_time = datetime.datetime.now()
                return {
                    'status': 'lost',
                    'message': f'Game over! The word was "{self.current_word}"',
                    'correct': False
                }
            else:
                remaining = self.max_wrong_guesses - self.wrong_guesses
                return {
                    'status': 'wrong',
                    'message': f'Sorry, "{letter}" is not in the word. {remaining} guesses remaining',
                    'correct': False
                }
    
    def guess_word(self, word: str) -> Dict:
        """Guess the entire word"""
        word = word.upper().strip()
        
        if self.game_over:
            return {'status': 'game_over', 'message': 'Game is already over'}
        
        if word == self.current_word:
            self.game_won = True
            self.game_over = True
            self.end_time = datetime.datetime.now()
            return {
                'status': 'won',
                'message': f'Excellent! You guessed the word "{self.current_word}"!',
                'correct': True
            }
        else:
            self.wrong_guesses += 1
            
            if self.wrong_guesses >= self.max_wrong_guesses:
                self.game_over = True
                self.end_time = datetime.datetime.now()
                return {
                    'status': 'lost',
                    'message': f'Wrong word! Game over! The word was "{self.current_word}"',
                    'correct': False
                }
            else:
                remaining = self.max_wrong_guesses - self.wrong_guesses
                return {
                    'status': 'wrong',
                    'message': f'Wrong word! {remaining} guesses remaining',
                    'correct': False
                }
    
    def is_word_complete(self) -> bool:
        """Check if all letters in the word have been guessed"""
        return all(letter in self.guessed_letters for letter in self.current_word if letter.isalpha())
    
    def get_display_word(self) -> str:
        """Get the word with guessed letters revealed and others as underscores"""
        display = ""
        for letter in self.current_word:
            if letter.isalpha():
                if letter in self.guessed_letters:
                    display += letter + " "
                else:
                    display += "_ "
            else:
                display += letter + " "
        return display.strip()
    
    def get_hangman_display(self) -> str:
        """Get current hangman drawing"""
        return self.hangman_stages[min(self.wrong_guesses, len(self.hangman_stages) - 1)]
    
    def get_game_duration(self) -> int:
        """Get game duration in seconds"""
        if self.start_time and self.end_time:
            return int((self.end_time - self.start_time).total_seconds())
        return 0
    
    def save_game_result(self, player_id: int):
        """Save game result to database"""
        if self.current_word_data and self.game_over:
            duration = self.get_game_duration()
            self.db.save_game(
                player_id=player_id,
                word_id=self.current_word_data['id'],
                guessed_letters=self.guessed_letters,
                wrong_guesses=self.wrong_guesses,
                max_wrong_guesses=self.max_wrong_guesses,
                won=self.game_won,
                duration=duration
            )
 
class HangmanGUI:
    """GUI for Hangman game"""
    
    def __init__(self):
        self.db = HangmanDatabase()
        self.game = HangmanGame(self.db)
        self.current_player = None
        
        self.root = tk.Tk()
        self.root.title("Advanced Hangman Game")
        self.root.geometry("900x700")
        
        self.setup_ui()
        self.show_login_screen()
    
    def setup_ui(self):
        """Setup the user interface"""
        # Create notebook for tabs
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # Game tab
        self.game_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.game_frame, text="Game")
        self.setup_game_tab()
        
        # Stats tab
        self.stats_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.stats_frame, text="Statistics")
        self.setup_stats_tab()
        
        # Leaderboard tab
        self.leaderboard_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.leaderboard_frame, text="Leaderboard")
        self.setup_leaderboard_tab()
        
        # Words tab
        self.words_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.words_frame, text="Add Words")
        self.setup_words_tab()
    
    def setup_game_tab(self):
        """Setup game tab"""
        # Main game frame
        main_frame = ttk.Frame(self.game_frame, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # Player info
        player_frame = ttk.LabelFrame(main_frame, text="Player", padding="5")
        player_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.player_label = ttk.Label(player_frame, text="Not logged in", font=("TkDefaultFont", 10, "bold"))
        self.player_label.pack(side=tk.LEFT)
        
        ttk.Button(player_frame, text="Logout", command=self.logout).pack(side=tk.RIGHT)
        
        # Game controls
        controls_frame = ttk.LabelFrame(main_frame, text="Game Controls", padding="5")
        controls_frame.pack(fill=tk.X, pady=(0, 10))
        
        # Category and difficulty selection
        settings_frame = ttk.Frame(controls_frame)
        settings_frame.pack(fill=tk.X, pady=(0, 5))
        
        ttk.Label(settings_frame, text="Category:").pack(side=tk.LEFT)
        self.category_var = tk.StringVar(value="any")
        self.category_combo = ttk.Combobox(settings_frame, textvariable=self.category_var, width=15)
        self.category_combo.pack(side=tk.LEFT, padx=(5, 10))
        
        ttk.Label(settings_frame, text="Difficulty:").pack(side=tk.LEFT)
        self.difficulty_var = tk.StringVar(value="any")
        difficulty_combo = ttk.Combobox(settings_frame, textvariable=self.difficulty_var, 
                                       values=["any", "1", "2", "3", "4", "5"], width=10)
        difficulty_combo.pack(side=tk.LEFT, padx=(5, 10))
        
        ttk.Button(controls_frame, text="New Game", command=self.start_new_game).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(controls_frame, text="Hint", command=self.show_hint).pack(side=tk.LEFT)
        
        # Game display
        game_display_frame = ttk.Frame(main_frame)
        game_display_frame.pack(fill=tk.BOTH, expand=True)
        
        # Left side - hangman drawing
        left_frame = ttk.Frame(game_display_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        self.hangman_text = tk.Text(left_frame, height=10, width=20, font=("Courier", 12), state=tk.DISABLED)
        self.hangman_text.pack(pady=10)
        
        # Right side - game info
        right_frame = ttk.Frame(game_display_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        
        # Word display
        self.word_label = ttk.Label(right_frame, text="", font=("TkDefaultFont", 16, "bold"))
        self.word_label.pack(pady=10)
        
        # Category and difficulty info
        self.info_label = ttk.Label(right_frame, text="")
        self.info_label.pack()
        
        # Guessed letters
        self.guessed_label = ttk.Label(right_frame, text="Guessed letters: ")
        self.guessed_label.pack(pady=(10, 5))
        
        # Game status
        self.status_label = ttk.Label(right_frame, text="Click 'New Game' to start", foreground="blue")
        self.status_label.pack(pady=5)
        
        # Input frame
        input_frame = ttk.Frame(main_frame)
        input_frame.pack(fill=tk.X, pady=10)
        
        # Letter input
        letter_frame = ttk.LabelFrame(input_frame, text="Guess Letter", padding="5")
        letter_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        
        self.letter_var = tk.StringVar()
        self.letter_entry = ttk.Entry(letter_frame, textvariable=self.letter_var, width=5, font=("TkDefaultFont", 14))
        self.letter_entry.pack(side=tk.LEFT, padx=(0, 5))
        self.letter_entry.bind('<Return>', lambda e: self.guess_letter())
        self.letter_entry.bind('<KeyRelease>', self.on_letter_keyrelease)
        
        ttk.Button(letter_frame, text="Guess Letter", command=self.guess_letter).pack(side=tk.LEFT)
        
        # Word input
        word_frame = ttk.LabelFrame(input_frame, text="Guess Word", padding="5")
        word_frame.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(5, 0))
        
        self.word_var = tk.StringVar()
        self.word_entry = ttk.Entry(word_frame, textvariable=self.word_var, font=("TkDefaultFont", 14))
        self.word_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        self.word_entry.bind('<Return>', lambda e: self.guess_word())
        
        ttk.Button(word_frame, text="Guess Word", command=self.guess_word).pack(side=tk.LEFT)
        
        self.update_categories()
    
    def setup_stats_tab(self):
        """Setup statistics tab"""
        stats_main = ttk.Frame(self.stats_frame, padding="10")
        stats_main.pack(fill=tk.BOTH, expand=True)
        
        # Stats display
        self.stats_text = scrolledtext.ScrolledText(stats_main, height=25, state=tk.DISABLED)
        self.stats_text.pack(fill=tk.BOTH, expand=True)
        
        # Refresh button
        ttk.Button(stats_main, text="Refresh Stats", command=self.update_stats).pack(pady=10)
    
    def setup_leaderboard_tab(self):
        """Setup leaderboard tab"""
        leader_main = ttk.Frame(self.leaderboard_frame, padding="10")
        leader_main.pack(fill=tk.BOTH, expand=True)
        
        # Leaderboard display
        self.leaderboard_text = scrolledtext.ScrolledText(leader_main, height=25, state=tk.DISABLED)
        self.leaderboard_text.pack(fill=tk.BOTH, expand=True)
        
        # Refresh button
        ttk.Button(leader_main, text="Refresh Leaderboard", command=self.update_leaderboard).pack(pady=10)
    
    def setup_words_tab(self):
        """Setup add words tab"""
        words_main = ttk.Frame(self.words_frame, padding="10")
        words_main.pack(fill=tk.BOTH, expand=True)
        
        # Add word form
        form_frame = ttk.LabelFrame(words_main, text="Add New Word", padding="10")
        form_frame.pack(fill=tk.X, pady=(0, 10))
        
        # Word input
        ttk.Label(form_frame, text="Word:").grid(row=0, column=0, sticky=tk.W, pady=2)
        self.new_word_var = tk.StringVar()
        ttk.Entry(form_frame, textvariable=self.new_word_var, width=20).grid(row=0, column=1, padx=(5, 0), pady=2, sticky=tk.W)
        
        # Category input
        ttk.Label(form_frame, text="Category:").grid(row=1, column=0, sticky=tk.W, pady=2)
        self.new_category_var = tk.StringVar()
        ttk.Entry(form_frame, textvariable=self.new_category_var, width=20).grid(row=1, column=1, padx=(5, 0), pady=2, sticky=tk.W)
        
        # Difficulty input
        ttk.Label(form_frame, text="Difficulty (1-5):").grid(row=2, column=0, sticky=tk.W, pady=2)
        self.new_difficulty_var = tk.StringVar(value="1")
        ttk.Spinbox(form_frame, from_=1, to=5, textvariable=self.new_difficulty_var, width=18).grid(row=2, column=1, padx=(5, 0), pady=2, sticky=tk.W)
        
        # Hint input
        ttk.Label(form_frame, text="Hint:").grid(row=3, column=0, sticky=tk.W, pady=2)
        self.new_hint_var = tk.StringVar()
        ttk.Entry(form_frame, textvariable=self.new_hint_var, width=40).grid(row=3, column=1, padx=(5, 0), pady=2, sticky=tk.W)
        
        # Add button
        ttk.Button(form_frame, text="Add Word", command=self.add_word).grid(row=4, column=1, padx=(5, 0), pady=10, sticky=tk.W)
        
        # Instructions
        instructions = """
Instructions for adding words:
• Enter a single word (no spaces)
• Choose an appropriate category
• Set difficulty from 1 (easy) to 5 (very hard)
• Provide a helpful hint
• Words will be automatically converted to lowercase
        """
        
        ttk.Label(words_main, text=instructions, justify=tk.LEFT).pack(anchor=tk.W)
    
    def show_login_screen(self):
        """Show login/register dialog"""
        login_window = tk.Toplevel(self.root)
        login_window.title("Login / Register")
        login_window.geometry("400x300")
        login_window.transient(self.root)
        login_window.grab_set()
        
        # Center the window
        login_window.geometry("+%d+%d" % (self.root.winfo_rootx() + 250, self.root.winfo_rooty() + 200))
        
        main_frame = ttk.Frame(login_window, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        ttk.Label(main_frame, text="Welcome to Hangman!", font=("TkDefaultFont", 16, "bold")).pack(pady=(0, 20))
        
        # Username
        ttk.Label(main_frame, text="Username:").pack(anchor=tk.W)
        username_var = tk.StringVar()
        username_entry = ttk.Entry(main_frame, textvariable=username_var, width=30)
        username_entry.pack(fill=tk.X, pady=(5, 10))
        
        # Password
        ttk.Label(main_frame, text="Password:").pack(anchor=tk.W)
        password_var = tk.StringVar()
        password_entry = ttk.Entry(main_frame, textvariable=password_var, width=30, show="*")
        password_entry.pack(fill=tk.X, pady=(5, 20))
        
        # Buttons
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X)
        
        def login():
            username = username_var.get().strip()
            password = password_var.get()
            
            if not username or not password:
                messagebox.showerror("Error", "Please enter both username and password")
                return
            
            player = self.db.authenticate_player(username, password)
            if player:
                self.current_player = player
                self.player_label.config(text=f"Player: {player['username']} | Wins: {player['total_wins']} | Streak: {player['current_streak']}")
                login_window.destroy()
                self.update_stats()
                self.update_leaderboard()
            else:
                messagebox.showerror("Error", "Invalid username or password")
        
        def register():
            username = username_var.get().strip()
            password = password_var.get()
            
            if not username or not password:
                messagebox.showerror("Error", "Please enter both username and password")
                return
            
            if len(username) < 3:
                messagebox.showerror("Error", "Username must be at least 3 characters long")
                return
            
            if len(password) < 6:
                messagebox.showerror("Error", "Password must be at least 6 characters long")
                return
            
            if self.db.create_player(username, password):
                messagebox.showinfo("Success", "Account created successfully! You can now log in.")
                # Auto-login
                player = self.db.authenticate_player(username, password)
                if player:
                    self.current_player = player
                    self.player_label.config(text=f"Player: {player['username']} | Wins: {player['total_wins']} | Streak: {player['current_streak']}")
                    login_window.destroy()
                    self.update_stats()
                    self.update_leaderboard()
            else:
                messagebox.showerror("Error", "Username already exists")
        
        ttk.Button(button_frame, text="Login", command=login).pack(side=tk.LEFT, padx=(0, 10))
        ttk.Button(button_frame, text="Register", command=register).pack(side=tk.LEFT)
        
        # Enter key bindings
        username_entry.bind('<Return>', lambda e: password_entry.focus())
        password_entry.bind('<Return>', lambda e: login())
        
        username_entry.focus()
    
    def logout(self):
        """Logout current player"""
        self.current_player = None
        self.player_label.config(text="Not logged in")
        self.game.reset_game()
        self.update_display()
        self.show_login_screen()
    
    def update_categories(self):
        """Update category dropdown"""
        categories = ["any"] + self.db.get_categories()
        self.category_combo['values'] = categories
    
    def start_new_game(self):
        """Start a new hangman game"""
        if not self.current_player:
            messagebox.showerror("Error", "Please log in first")
            return
        
        category = self.category_var.get() if self.category_var.get() != "any" else None
        difficulty = int(self.difficulty_var.get()) if self.difficulty_var.get() != "any" else None
        
        if self.game.start_new_game(category, difficulty):
            self.update_display()
            self.letter_entry.focus()
            self.status_label.config(text="Game started! Guess a letter or the whole word.", foreground="blue")
        else:
            messagebox.showerror("Error", "No words found with the selected criteria")
    
    def guess_letter(self):
        """Process letter guess"""
        if not self.current_player:
            return
        
        letter = self.letter_var.get().strip()
        if not letter:
            return
        
        result = self.game.make_guess(letter)
        self.letter_var.set("")
        
        self.update_display()
        self.show_guess_result(result)
        
        if self.game.game_over:
            self.game.save_game_result(self.current_player['id'])
            self.update_player_info()
    
    def guess_word(self):
        """Process word guess"""
        if not self.current_player:
            return
        
        word = self.word_var.get().strip()
        if not word:
            return
        
        result = self.game.guess_word(word)
        self.word_var.set("")
        
        self.update_display()
        self.show_guess_result(result)
        
        if self.game.game_over:
            self.game.save_game_result(self.current_player['id'])
            self.update_player_info()
    
    def show_hint(self):
        """Show hint for current word"""
        if self.game.current_word_data and self.game.current_word_data['hint']:
            hint = self.game.current_word_data['hint']
            messagebox.showinfo("Hint", f"Hint: {hint}")
        else:
            messagebox.showinfo("Hint", "No hint available for this word")
    
    def on_letter_keyrelease(self, event):
        """Handle letter entry key release"""
        # Limit to single character and convert to uppercase
        text = self.letter_var.get().upper()
        if len(text) > 1:
            self.letter_var.set(text[-1])
        elif text and not text.isalpha():
            self.letter_var.set("")
    
    def update_display(self):
        """Update game display"""
        # Update hangman drawing
        self.hangman_text.config(state=tk.NORMAL)
        self.hangman_text.delete(1.0, tk.END)
        self.hangman_text.insert(tk.END, self.game.get_hangman_display())
        self.hangman_text.config(state=tk.DISABLED)
        
        # Update word display
        if self.game.current_word:
            self.word_label.config(text=self.game.get_display_word())
            
            # Update info
            category = self.game.current_word_data['category'].title()
            difficulty = self.game.current_word_data['difficulty']
            self.info_label.config(text=f"Category: {category} | Difficulty: {difficulty}")
        else:
            self.word_label.config(text="Click 'New Game' to start")
            self.info_label.config(text="")
        
        # Update guessed letters
        if self.game.guessed_letters:
            guessed = ", ".join(sorted(self.game.guessed_letters))
            self.guessed_label.config(text=f"Guessed letters: {guessed}")
        else:
            self.guessed_label.config(text="Guessed letters: ")
    
    def show_guess_result(self, result):
        """Show result of guess"""
        status = result['status']
        message = result['message']
        
        if status == 'won':
            self.status_label.config(text=message, foreground="green")
        elif status == 'lost':
            self.status_label.config(text=message, foreground="red")
        elif status == 'correct':
            self.status_label.config(text=message, foreground="blue")
        elif status == 'wrong':
            self.status_label.config(text=message, foreground="orange")
        else:
            self.status_label.config(text=message, foreground="gray")
    
    def update_player_info(self):
        """Update player info after game"""
        if self.current_player:
            # Refresh player stats
            updated_player = self.db.authenticate_player(
                self.current_player['username'], 
                ""  # We'll need to modify this to not require password for refresh
            )
            if updated_player:
                self.current_player = updated_player
                self.player_label.config(text=f"Player: {updated_player['username']} | Wins: {updated_player['total_wins']} | Streak: {updated_player['current_streak']}")
    
    def add_word(self):
        """Add new word to database"""
        word = self.new_word_var.get().strip()
        category = self.new_category_var.get().strip()
        hint = self.new_hint_var.get().strip()
        
        try:
            difficulty = int(self.new_difficulty_var.get())
        except ValueError:
            messagebox.showerror("Error", "Difficulty must be a number between 1 and 5")
            return
        
        if not word or not category:
            messagebox.showerror("Error", "Please enter both word and category")
            return
        
        if not word.replace(" ", "").isalpha():
            messagebox.showerror("Error", "Word should contain only letters (spaces will be removed)")
            return
        
        # Remove spaces from word
        word = word.replace(" ", "")
        
        if self.db.add_word(word, category, difficulty, hint):
            messagebox.showinfo("Success", f"Word '{word}' added successfully!")
            
            # Clear form
            self.new_word_var.set("")
            self.new_category_var.set("")
            self.new_difficulty_var.set("1")
            self.new_hint_var.set("")
            
            # Update categories
            self.update_categories()
        else:
            messagebox.showerror("Error", "Word already exists in database")
    
    def update_stats(self):
        """Update statistics display"""
        if not self.current_player:
            return
        
        stats = self.db.get_player_stats(self.current_player['id'])
        
        self.stats_text.config(state=tk.NORMAL)
        self.stats_text.delete(1.0, tk.END)
        
        # Basic stats
        self.stats_text.insert(tk.END, f"=== Statistics for {stats['username']} ===\n\n")
        self.stats_text.insert(tk.END, f"Member since: {stats['member_since'][:10]}\n")
        self.stats_text.insert(tk.END, f"Total games: {stats['total_games']}\n")
        self.stats_text.insert(tk.END, f"Total wins: {stats['total_wins']}\n")
        self.stats_text.insert(tk.END, f"Total losses: {stats['total_losses']}\n")
        self.stats_text.insert(tk.END, f"Win rate: {stats['win_rate']:.1f}%\n")
        self.stats_text.insert(tk.END, f"Best streak: {stats['best_streak']}\n")
        self.stats_text.insert(tk.END, f"Current streak: {stats['current_streak']}\n\n")
        
        # Category performance
        if stats['category_stats']:
            self.stats_text.insert(tk.END, "=== Performance by Category ===\n")
            for category, games, wins in stats['category_stats']:
                win_rate = (wins / games * 100) if games > 0 else 0
                self.stats_text.insert(tk.END, f"{category.title()}: {wins}/{games} ({win_rate:.1f}%)\n")
            self.stats_text.insert(tk.END, "\n")
        
        # Recent games
        if stats['recent_games']:
            self.stats_text.insert(tk.END, "=== Recent Games ===\n")
            for won, duration, date, word, category in stats['recent_games']:
                result = "Won" if won else "Lost"
                duration_str = f"{duration}s" if duration else "N/A"
                date_str = date[:10]  # Just the date part
                self.stats_text.insert(tk.END, f"{date_str}: {result} - {word.upper()} ({category}) - {duration_str}\n")
        
        self.stats_text.config(state=tk.DISABLED)
    
    def update_leaderboard(self):
        """Update leaderboard display"""
        leaderboard = self.db.get_leaderboard(20)
        
        self.leaderboard_text.config(state=tk.NORMAL)
        self.leaderboard_text.delete(1.0, tk.END)
        
        self.leaderboard_text.insert(tk.END, "=== LEADERBOARD ===\n\n")
        self.leaderboard_text.insert(tk.END, f"{'Rank':<5}{'Player':<15}{'Wins':<6}{'Games':<7}{'Win%':<6}{'Streak':<8}\n")
        self.leaderboard_text.insert(tk.END, "-" * 60 + "\n")
        
        for i, player in enumerate(leaderboard, 1):
            rank = f"{i}."
            username = player['username'][:14]
            wins = str(player['total_wins'])
            games = str(player['total_games'])
            win_rate = f"{player['win_rate']}%"
            streak = str(player['best_streak'])
            
            line = f"{rank:<5}{username:<15}{wins:<6}{games:<7}{win_rate:<6}{streak:<8}\n"
            self.leaderboard_text.insert(tk.END, line)
        
        self.leaderboard_text.config(state=tk.DISABLED)
    
    def run(self):
        """Run the application"""
        self.root.mainloop()
 
def main():
    """Main function"""
    app = HangmanGUI()
    app.run()
 
if __name__ == "__main__":
    main()
 
Hangman Game with Database Integration
# Advanced Hangman Game with Database
 
import sqlite3
import random
import json
import hashlib
import datetime
from typing import List, Dict, Optional, Tuple
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import requests
from pathlib import Path
import re
import string
 
class HangmanDatabase:
    """Database manager for hangman game"""
    
    def __init__(self, db_path: str = "hangman.db"):
        self.db_path = db_path
        self.init_database()
    
    def init_database(self):
        """Initialize database tables"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            
            # Words table
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS words (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    word TEXT UNIQUE NOT NULL,
                    category TEXT NOT NULL,
                    difficulty INTEGER NOT NULL,
                    hint TEXT,
                    added_date TEXT NOT NULL,
                    used_count INTEGER DEFAULT 0
                )
            """)
            
            # Players table
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS players (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    username TEXT UNIQUE NOT NULL,
                    password_hash TEXT NOT NULL,
                    created_date TEXT NOT NULL,
                    last_login TEXT,
                    total_games INTEGER DEFAULT 0,
                    total_wins INTEGER DEFAULT 0,
                    total_losses INTEGER DEFAULT 0,
                    best_streak INTEGER DEFAULT 0,
                    current_streak INTEGER DEFAULT 0
                )
            """)
            
            # Games table
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS games (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    player_id INTEGER NOT NULL,
                    word_id INTEGER NOT NULL,
                    guessed_letters TEXT NOT NULL,
                    wrong_guesses INTEGER NOT NULL,
                    max_wrong_guesses INTEGER NOT NULL,
                    won BOOLEAN NOT NULL,
                    duration_seconds INTEGER,
                    game_date TEXT NOT NULL,
                    FOREIGN KEY (player_id) REFERENCES players (id),
                    FOREIGN KEY (word_id) REFERENCES words (id)
                )
            """)
            
            # Achievements table
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS achievements (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    player_id INTEGER NOT NULL,
                    achievement_type TEXT NOT NULL,
                    achievement_data TEXT,
                    earned_date TEXT NOT NULL,
                    FOREIGN KEY (player_id) REFERENCES players (id)
                )
            """)
            
            conn.commit()
            
            # Add default words if database is empty
            self.add_default_words()
    
    def add_default_words(self):
        """Add default word list if database is empty"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT COUNT(*) FROM words")
            if cursor.fetchone()[0] == 0:
                default_words = [
                    # Animals
                    ("elephant", "animals", 2, "Large mammal with trunk"),
                    ("giraffe", "animals", 2, "Tallest animal in the world"),
                    ("penguin", "animals", 2, "Flightless bird from Antarctica"),
                    ("dolphin", "animals", 2, "Intelligent marine mammal"),
                    ("butterfly", "animals", 3, "Colorful flying insect"),
                    ("rhinoceros", "animals", 4, "Large horned mammal"),
                    ("hippopotamus", "animals", 4, "Large water-loving mammal"),
                    
                    # Countries
                    ("france", "countries", 2, "European country known for Eiffel Tower"),
                    ("japan", "countries", 1, "Island nation in East Asia"),
                    ("brazil", "countries", 2, "Largest South American country"),
                    ("australia", "countries", 3, "Island continent"),
                    ("switzerland", "countries", 4, "Neutral European country"),
                    ("madagascar", "countries", 4, "Large island off Africa"),
                    
                    # Technology
                    ("computer", "technology", 2, "Electronic device for processing data"),
                    ("internet", "technology", 2, "Global network of computers"),
                    ("smartphone", "technology", 3, "Portable communication device"),
                    ("artificial", "technology", 4, "Type of intelligence in machines"),
                    ("programming", "technology", 4, "Process of creating software"),
                    ("cybersecurity", "technology", 5, "Protection of digital systems"),
                    
                    # Science
                    ("gravity", "science", 2, "Force that pulls objects down"),
                    ("molecule", "science", 3, "Smallest unit of a compound"),
                    ("photosynthesis", "science", 5, "Process plants use to make food"),
                    ("ecosystem", "science", 3, "Community of living organisms"),
                    ("chromosome", "science", 4, "Structure containing DNA"),
                    ("quantum", "science", 3, "Related to atomic particles"),
                    
                    # Sports
                    ("basketball", "sports", 3, "Sport played with orange ball"),
                    ("swimming", "sports", 2, "Water sport activity"),
                    ("marathon", "sports", 3, "Long distance running race"),
                    ("gymnastics", "sports", 4, "Sport involving flexibility and strength"),
                    ("badminton", "sports", 3, "Racquet sport with shuttlecock"),
                    
                    # Food
                    ("chocolate", "food", 2, "Sweet treat made from cocoa"),
                    ("spaghetti", "food", 3, "Long thin pasta"),
                    ("hamburger", "food", 3, "Ground meat sandwich"),
                    ("pizza", "food", 1, "Italian flatbread with toppings"),
                    ("sandwich", "food", 2, "Food between two pieces of bread"),
                ]
                
                for word, category, difficulty, hint in default_words:
                    self.add_word(word, category, difficulty, hint)
    
    def add_word(self, word: str, category: str, difficulty: int, hint: str = ""):
        """Add a new word to database"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            try:
                cursor.execute("""
                    INSERT INTO words (word, category, difficulty, hint, added_date)
                    VALUES (?, ?, ?, ?, ?)
                """, (word.lower(), category.lower(), difficulty, hint, datetime.datetime.now().isoformat()))
                conn.commit()
                return True
            except sqlite3.IntegrityError:
                return False  # Word already exists
    
    def get_random_word(self, category: str = None, difficulty: int = None) -> Optional[Dict]:
        """Get a random word from database"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            
            query = "SELECT id, word, category, difficulty, hint FROM words WHERE 1=1"
            params = []
            
            if category:
                query += " AND category = ?"
                params.append(category.lower())
            
            if difficulty:
                query += " AND difficulty = ?"
                params.append(difficulty)
            
            cursor.execute(query, params)
            words = cursor.fetchall()
            
            if words:
                word_data = random.choice(words)
                return {
                    'id': word_data[0],
                    'word': word_data[1],
                    'category': word_data[2],
                    'difficulty': word_data[3],
                    'hint': word_data[4]
                }
            return None
    
    def get_categories(self) -> List[str]:
        """Get all available categories"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT DISTINCT category FROM words ORDER BY category")
            return [row[0] for row in cursor.fetchall()]
    
    def create_player(self, username: str, password: str) -> bool:
        """Create a new player account"""
        password_hash = hashlib.sha256(password.encode()).hexdigest()
        
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            try:
                cursor.execute("""
                    INSERT INTO players (username, password_hash, created_date)
                    VALUES (?, ?, ?)
                """, (username, password_hash, datetime.datetime.now().isoformat()))
                conn.commit()
                return True
            except sqlite3.IntegrityError:
                return False  # Username already exists
    
    def authenticate_player(self, username: str, password: str) -> Optional[Dict]:
        """Authenticate player login"""
        password_hash = hashlib.sha256(password.encode()).hexdigest()
        
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("""
                SELECT id, username, total_games, total_wins, total_losses, 
                       best_streak, current_streak
                FROM players 
                WHERE username = ? AND password_hash = ?
            """, (username, password_hash))
            
            result = cursor.fetchone()
            if result:
                # Update last login
                cursor.execute("""
                    UPDATE players SET last_login = ? WHERE id = ?
                """, (datetime.datetime.now().isoformat(), result[0]))
                conn.commit()
                
                return {
                    'id': result[0],
                    'username': result[1],
                    'total_games': result[2],
                    'total_wins': result[3],
                    'total_losses': result[4],
                    'best_streak': result[5],
                    'current_streak': result[6]
                }
            return None
    
    def save_game(self, player_id: int, word_id: int, guessed_letters: List[str], 
                  wrong_guesses: int, max_wrong_guesses: int, won: bool, duration: int):
        """Save game result to database"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            
            # Save game
            cursor.execute("""
                INSERT INTO games (player_id, word_id, guessed_letters, wrong_guesses,
                                 max_wrong_guesses, won, duration_seconds, game_date)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """, (player_id, word_id, json.dumps(guessed_letters), wrong_guesses,
                  max_wrong_guesses, won, duration, datetime.datetime.now().isoformat()))
            
            # Update player stats
            if won:
                cursor.execute("""
                    UPDATE players 
                    SET total_games = total_games + 1, 
                        total_wins = total_wins + 1,
                        current_streak = current_streak + 1
                    WHERE id = ?
                """, (player_id,))
                
                # Check for new best streak
                cursor.execute("SELECT current_streak, best_streak FROM players WHERE id = ?", (player_id,))
                current, best = cursor.fetchone()
                if current > best:
                    cursor.execute("UPDATE players SET best_streak = ? WHERE id = ?", (current, player_id))
            else:
                cursor.execute("""
                    UPDATE players 
                    SET total_games = total_games + 1, 
                        total_losses = total_losses + 1,
                        current_streak = 0
                    WHERE id = ?
                """, (player_id,))
            
            # Update word usage count
            cursor.execute("UPDATE words SET used_count = used_count + 1 WHERE id = ?", (word_id,))
            
            conn.commit()
    
    def get_player_stats(self, player_id: int) -> Dict:
        """Get detailed player statistics"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            
            # Basic stats
            cursor.execute("""
                SELECT username, total_games, total_wins, total_losses, 
                       best_streak, current_streak, created_date
                FROM players WHERE id = ?
            """, (player_id,))
            
            player_data = cursor.fetchone()
            
            # Recent games
            cursor.execute("""
                SELECT g.won, g.duration_seconds, g.game_date, w.word, w.category
                FROM games g
                JOIN words w ON g.word_id = w.id
                WHERE g.player_id = ?
                ORDER BY g.game_date DESC
                LIMIT 10
            """, (player_id,))
            
            recent_games = cursor.fetchall()
            
            # Category performance
            cursor.execute("""
                SELECT w.category, COUNT(*) as games, SUM(g.won) as wins
                FROM games g
                JOIN words w ON g.word_id = w.id
                WHERE g.player_id = ?
                GROUP BY w.category
            """, (player_id,))
            
            category_stats = cursor.fetchall()
            
            win_rate = (player_data[2] / player_data[1] * 100) if player_data[1] > 0 else 0
            
            return {
                'username': player_data[0],
                'total_games': player_data[1],
                'total_wins': player_data[2],
                'total_losses': player_data[3],
                'win_rate': win_rate,
                'best_streak': player_data[4],
                'current_streak': player_data[5],
                'member_since': player_data[6],
                'recent_games': recent_games,
                'category_stats': category_stats
            }
    
    def get_leaderboard(self, limit: int = 10) -> List[Dict]:
        """Get top players leaderboard"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("""
                SELECT username, total_games, total_wins, total_losses, 
                       best_streak, current_streak,
                       CASE WHEN total_games > 0 THEN ROUND(total_wins * 100.0 / total_games, 1) ELSE 0 END as win_rate
                FROM players
                WHERE total_games > 0
                ORDER BY total_wins DESC, win_rate DESC, best_streak DESC
                LIMIT ?
            """, (limit,))
            
            return [
                {
                    'username': row[0],
                    'total_games': row[1],
                    'total_wins': row[2],
                    'total_losses': row[3],
                    'best_streak': row[4],
                    'current_streak': row[5],
                    'win_rate': row[6]
                }
                for row in cursor.fetchall()
            ]
 
class HangmanGame:
    """Hangman game logic"""
    
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.reset_game()
        
        # Hangman drawings
        self.hangman_stages = [
            """
   +---+
   |   |
       |
       |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
       |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
   |   |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
  /|   |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
  /|\\  |
       |
       |
=========
""",
            """
   +---+
   |   |
   O   |
  /|\\  |
  /    |
       |
=========
""",
            """
   +---+
   |   |
   O   |
  /|\\  |
  / \\  |
       |
=========
"""
        ]
    
    def reset_game(self):
        """Reset game state"""
        self.current_word_data = None
        self.current_word = ""
        self.guessed_letters = []
        self.wrong_guesses = 0
        self.max_wrong_guesses = 6
        self.game_won = False
        self.game_over = False
        self.start_time = None
        self.end_time = None
    
    def start_new_game(self, category: str = None, difficulty: int = None) -> bool:
        """Start a new game"""
        self.reset_game()
        
        self.current_word_data = self.db.get_random_word(category, difficulty)
        if not self.current_word_data:
            return False
        
        self.current_word = self.current_word_data['word'].upper()
        self.start_time = datetime.datetime.now()
        return True
    
    def make_guess(self, letter: str) -> Dict:
        """Make a letter guess"""
        letter = letter.upper()
        
        if self.game_over:
            return {'status': 'game_over', 'message': 'Game is already over'}
        
        if letter in self.guessed_letters:
            return {'status': 'already_guessed', 'message': f'You already guessed "{letter}"'}
        
        if not letter.isalpha() or len(letter) != 1:
            return {'status': 'invalid', 'message': 'Please enter a single letter'}
        
        self.guessed_letters.append(letter)
        
        if letter in self.current_word:
            # Correct guess
            if self.is_word_complete():
                self.game_won = True
                self.game_over = True
                self.end_time = datetime.datetime.now()
                return {
                    'status': 'won',
                    'message': f'Congratulations! You won! The word was "{self.current_word}"',
                    'correct': True
                }
            else:
                return {
                    'status': 'correct',
                    'message': f'Good guess! "{letter}" is in the word',
                    'correct': True
                }
        else:
            # Wrong guess
            self.wrong_guesses += 1
            
            if self.wrong_guesses >= self.max_wrong_guesses:
                self.game_over = True
                self.end_time = datetime.datetime.now()
                return {
                    'status': 'lost',
                    'message': f'Game over! The word was "{self.current_word}"',
                    'correct': False
                }
            else:
                remaining = self.max_wrong_guesses - self.wrong_guesses
                return {
                    'status': 'wrong',
                    'message': f'Sorry, "{letter}" is not in the word. {remaining} guesses remaining',
                    'correct': False
                }
    
    def guess_word(self, word: str) -> Dict:
        """Guess the entire word"""
        word = word.upper().strip()
        
        if self.game_over:
            return {'status': 'game_over', 'message': 'Game is already over'}
        
        if word == self.current_word:
            self.game_won = True
            self.game_over = True
            self.end_time = datetime.datetime.now()
            return {
                'status': 'won',
                'message': f'Excellent! You guessed the word "{self.current_word}"!',
                'correct': True
            }
        else:
            self.wrong_guesses += 1
            
            if self.wrong_guesses >= self.max_wrong_guesses:
                self.game_over = True
                self.end_time = datetime.datetime.now()
                return {
                    'status': 'lost',
                    'message': f'Wrong word! Game over! The word was "{self.current_word}"',
                    'correct': False
                }
            else:
                remaining = self.max_wrong_guesses - self.wrong_guesses
                return {
                    'status': 'wrong',
                    'message': f'Wrong word! {remaining} guesses remaining',
                    'correct': False
                }
    
    def is_word_complete(self) -> bool:
        """Check if all letters in the word have been guessed"""
        return all(letter in self.guessed_letters for letter in self.current_word if letter.isalpha())
    
    def get_display_word(self) -> str:
        """Get the word with guessed letters revealed and others as underscores"""
        display = ""
        for letter in self.current_word:
            if letter.isalpha():
                if letter in self.guessed_letters:
                    display += letter + " "
                else:
                    display += "_ "
            else:
                display += letter + " "
        return display.strip()
    
    def get_hangman_display(self) -> str:
        """Get current hangman drawing"""
        return self.hangman_stages[min(self.wrong_guesses, len(self.hangman_stages) - 1)]
    
    def get_game_duration(self) -> int:
        """Get game duration in seconds"""
        if self.start_time and self.end_time:
            return int((self.end_time - self.start_time).total_seconds())
        return 0
    
    def save_game_result(self, player_id: int):
        """Save game result to database"""
        if self.current_word_data and self.game_over:
            duration = self.get_game_duration()
            self.db.save_game(
                player_id=player_id,
                word_id=self.current_word_data['id'],
                guessed_letters=self.guessed_letters,
                wrong_guesses=self.wrong_guesses,
                max_wrong_guesses=self.max_wrong_guesses,
                won=self.game_won,
                duration=duration
            )
 
class HangmanGUI:
    """GUI for Hangman game"""
    
    def __init__(self):
        self.db = HangmanDatabase()
        self.game = HangmanGame(self.db)
        self.current_player = None
        
        self.root = tk.Tk()
        self.root.title("Advanced Hangman Game")
        self.root.geometry("900x700")
        
        self.setup_ui()
        self.show_login_screen()
    
    def setup_ui(self):
        """Setup the user interface"""
        # Create notebook for tabs
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # Game tab
        self.game_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.game_frame, text="Game")
        self.setup_game_tab()
        
        # Stats tab
        self.stats_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.stats_frame, text="Statistics")
        self.setup_stats_tab()
        
        # Leaderboard tab
        self.leaderboard_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.leaderboard_frame, text="Leaderboard")
        self.setup_leaderboard_tab()
        
        # Words tab
        self.words_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.words_frame, text="Add Words")
        self.setup_words_tab()
    
    def setup_game_tab(self):
        """Setup game tab"""
        # Main game frame
        main_frame = ttk.Frame(self.game_frame, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # Player info
        player_frame = ttk.LabelFrame(main_frame, text="Player", padding="5")
        player_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.player_label = ttk.Label(player_frame, text="Not logged in", font=("TkDefaultFont", 10, "bold"))
        self.player_label.pack(side=tk.LEFT)
        
        ttk.Button(player_frame, text="Logout", command=self.logout).pack(side=tk.RIGHT)
        
        # Game controls
        controls_frame = ttk.LabelFrame(main_frame, text="Game Controls", padding="5")
        controls_frame.pack(fill=tk.X, pady=(0, 10))
        
        # Category and difficulty selection
        settings_frame = ttk.Frame(controls_frame)
        settings_frame.pack(fill=tk.X, pady=(0, 5))
        
        ttk.Label(settings_frame, text="Category:").pack(side=tk.LEFT)
        self.category_var = tk.StringVar(value="any")
        self.category_combo = ttk.Combobox(settings_frame, textvariable=self.category_var, width=15)
        self.category_combo.pack(side=tk.LEFT, padx=(5, 10))
        
        ttk.Label(settings_frame, text="Difficulty:").pack(side=tk.LEFT)
        self.difficulty_var = tk.StringVar(value="any")
        difficulty_combo = ttk.Combobox(settings_frame, textvariable=self.difficulty_var, 
                                       values=["any", "1", "2", "3", "4", "5"], width=10)
        difficulty_combo.pack(side=tk.LEFT, padx=(5, 10))
        
        ttk.Button(controls_frame, text="New Game", command=self.start_new_game).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(controls_frame, text="Hint", command=self.show_hint).pack(side=tk.LEFT)
        
        # Game display
        game_display_frame = ttk.Frame(main_frame)
        game_display_frame.pack(fill=tk.BOTH, expand=True)
        
        # Left side - hangman drawing
        left_frame = ttk.Frame(game_display_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        self.hangman_text = tk.Text(left_frame, height=10, width=20, font=("Courier", 12), state=tk.DISABLED)
        self.hangman_text.pack(pady=10)
        
        # Right side - game info
        right_frame = ttk.Frame(game_display_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        
        # Word display
        self.word_label = ttk.Label(right_frame, text="", font=("TkDefaultFont", 16, "bold"))
        self.word_label.pack(pady=10)
        
        # Category and difficulty info
        self.info_label = ttk.Label(right_frame, text="")
        self.info_label.pack()
        
        # Guessed letters
        self.guessed_label = ttk.Label(right_frame, text="Guessed letters: ")
        self.guessed_label.pack(pady=(10, 5))
        
        # Game status
        self.status_label = ttk.Label(right_frame, text="Click 'New Game' to start", foreground="blue")
        self.status_label.pack(pady=5)
        
        # Input frame
        input_frame = ttk.Frame(main_frame)
        input_frame.pack(fill=tk.X, pady=10)
        
        # Letter input
        letter_frame = ttk.LabelFrame(input_frame, text="Guess Letter", padding="5")
        letter_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        
        self.letter_var = tk.StringVar()
        self.letter_entry = ttk.Entry(letter_frame, textvariable=self.letter_var, width=5, font=("TkDefaultFont", 14))
        self.letter_entry.pack(side=tk.LEFT, padx=(0, 5))
        self.letter_entry.bind('<Return>', lambda e: self.guess_letter())
        self.letter_entry.bind('<KeyRelease>', self.on_letter_keyrelease)
        
        ttk.Button(letter_frame, text="Guess Letter", command=self.guess_letter).pack(side=tk.LEFT)
        
        # Word input
        word_frame = ttk.LabelFrame(input_frame, text="Guess Word", padding="5")
        word_frame.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(5, 0))
        
        self.word_var = tk.StringVar()
        self.word_entry = ttk.Entry(word_frame, textvariable=self.word_var, font=("TkDefaultFont", 14))
        self.word_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        self.word_entry.bind('<Return>', lambda e: self.guess_word())
        
        ttk.Button(word_frame, text="Guess Word", command=self.guess_word).pack(side=tk.LEFT)
        
        self.update_categories()
    
    def setup_stats_tab(self):
        """Setup statistics tab"""
        stats_main = ttk.Frame(self.stats_frame, padding="10")
        stats_main.pack(fill=tk.BOTH, expand=True)
        
        # Stats display
        self.stats_text = scrolledtext.ScrolledText(stats_main, height=25, state=tk.DISABLED)
        self.stats_text.pack(fill=tk.BOTH, expand=True)
        
        # Refresh button
        ttk.Button(stats_main, text="Refresh Stats", command=self.update_stats).pack(pady=10)
    
    def setup_leaderboard_tab(self):
        """Setup leaderboard tab"""
        leader_main = ttk.Frame(self.leaderboard_frame, padding="10")
        leader_main.pack(fill=tk.BOTH, expand=True)
        
        # Leaderboard display
        self.leaderboard_text = scrolledtext.ScrolledText(leader_main, height=25, state=tk.DISABLED)
        self.leaderboard_text.pack(fill=tk.BOTH, expand=True)
        
        # Refresh button
        ttk.Button(leader_main, text="Refresh Leaderboard", command=self.update_leaderboard).pack(pady=10)
    
    def setup_words_tab(self):
        """Setup add words tab"""
        words_main = ttk.Frame(self.words_frame, padding="10")
        words_main.pack(fill=tk.BOTH, expand=True)
        
        # Add word form
        form_frame = ttk.LabelFrame(words_main, text="Add New Word", padding="10")
        form_frame.pack(fill=tk.X, pady=(0, 10))
        
        # Word input
        ttk.Label(form_frame, text="Word:").grid(row=0, column=0, sticky=tk.W, pady=2)
        self.new_word_var = tk.StringVar()
        ttk.Entry(form_frame, textvariable=self.new_word_var, width=20).grid(row=0, column=1, padx=(5, 0), pady=2, sticky=tk.W)
        
        # Category input
        ttk.Label(form_frame, text="Category:").grid(row=1, column=0, sticky=tk.W, pady=2)
        self.new_category_var = tk.StringVar()
        ttk.Entry(form_frame, textvariable=self.new_category_var, width=20).grid(row=1, column=1, padx=(5, 0), pady=2, sticky=tk.W)
        
        # Difficulty input
        ttk.Label(form_frame, text="Difficulty (1-5):").grid(row=2, column=0, sticky=tk.W, pady=2)
        self.new_difficulty_var = tk.StringVar(value="1")
        ttk.Spinbox(form_frame, from_=1, to=5, textvariable=self.new_difficulty_var, width=18).grid(row=2, column=1, padx=(5, 0), pady=2, sticky=tk.W)
        
        # Hint input
        ttk.Label(form_frame, text="Hint:").grid(row=3, column=0, sticky=tk.W, pady=2)
        self.new_hint_var = tk.StringVar()
        ttk.Entry(form_frame, textvariable=self.new_hint_var, width=40).grid(row=3, column=1, padx=(5, 0), pady=2, sticky=tk.W)
        
        # Add button
        ttk.Button(form_frame, text="Add Word", command=self.add_word).grid(row=4, column=1, padx=(5, 0), pady=10, sticky=tk.W)
        
        # Instructions
        instructions = """
Instructions for adding words:
• Enter a single word (no spaces)
• Choose an appropriate category
• Set difficulty from 1 (easy) to 5 (very hard)
• Provide a helpful hint
• Words will be automatically converted to lowercase
        """
        
        ttk.Label(words_main, text=instructions, justify=tk.LEFT).pack(anchor=tk.W)
    
    def show_login_screen(self):
        """Show login/register dialog"""
        login_window = tk.Toplevel(self.root)
        login_window.title("Login / Register")
        login_window.geometry("400x300")
        login_window.transient(self.root)
        login_window.grab_set()
        
        # Center the window
        login_window.geometry("+%d+%d" % (self.root.winfo_rootx() + 250, self.root.winfo_rooty() + 200))
        
        main_frame = ttk.Frame(login_window, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        ttk.Label(main_frame, text="Welcome to Hangman!", font=("TkDefaultFont", 16, "bold")).pack(pady=(0, 20))
        
        # Username
        ttk.Label(main_frame, text="Username:").pack(anchor=tk.W)
        username_var = tk.StringVar()
        username_entry = ttk.Entry(main_frame, textvariable=username_var, width=30)
        username_entry.pack(fill=tk.X, pady=(5, 10))
        
        # Password
        ttk.Label(main_frame, text="Password:").pack(anchor=tk.W)
        password_var = tk.StringVar()
        password_entry = ttk.Entry(main_frame, textvariable=password_var, width=30, show="*")
        password_entry.pack(fill=tk.X, pady=(5, 20))
        
        # Buttons
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X)
        
        def login():
            username = username_var.get().strip()
            password = password_var.get()
            
            if not username or not password:
                messagebox.showerror("Error", "Please enter both username and password")
                return
            
            player = self.db.authenticate_player(username, password)
            if player:
                self.current_player = player
                self.player_label.config(text=f"Player: {player['username']} | Wins: {player['total_wins']} | Streak: {player['current_streak']}")
                login_window.destroy()
                self.update_stats()
                self.update_leaderboard()
            else:
                messagebox.showerror("Error", "Invalid username or password")
        
        def register():
            username = username_var.get().strip()
            password = password_var.get()
            
            if not username or not password:
                messagebox.showerror("Error", "Please enter both username and password")
                return
            
            if len(username) < 3:
                messagebox.showerror("Error", "Username must be at least 3 characters long")
                return
            
            if len(password) < 6:
                messagebox.showerror("Error", "Password must be at least 6 characters long")
                return
            
            if self.db.create_player(username, password):
                messagebox.showinfo("Success", "Account created successfully! You can now log in.")
                # Auto-login
                player = self.db.authenticate_player(username, password)
                if player:
                    self.current_player = player
                    self.player_label.config(text=f"Player: {player['username']} | Wins: {player['total_wins']} | Streak: {player['current_streak']}")
                    login_window.destroy()
                    self.update_stats()
                    self.update_leaderboard()
            else:
                messagebox.showerror("Error", "Username already exists")
        
        ttk.Button(button_frame, text="Login", command=login).pack(side=tk.LEFT, padx=(0, 10))
        ttk.Button(button_frame, text="Register", command=register).pack(side=tk.LEFT)
        
        # Enter key bindings
        username_entry.bind('<Return>', lambda e: password_entry.focus())
        password_entry.bind('<Return>', lambda e: login())
        
        username_entry.focus()
    
    def logout(self):
        """Logout current player"""
        self.current_player = None
        self.player_label.config(text="Not logged in")
        self.game.reset_game()
        self.update_display()
        self.show_login_screen()
    
    def update_categories(self):
        """Update category dropdown"""
        categories = ["any"] + self.db.get_categories()
        self.category_combo['values'] = categories
    
    def start_new_game(self):
        """Start a new hangman game"""
        if not self.current_player:
            messagebox.showerror("Error", "Please log in first")
            return
        
        category = self.category_var.get() if self.category_var.get() != "any" else None
        difficulty = int(self.difficulty_var.get()) if self.difficulty_var.get() != "any" else None
        
        if self.game.start_new_game(category, difficulty):
            self.update_display()
            self.letter_entry.focus()
            self.status_label.config(text="Game started! Guess a letter or the whole word.", foreground="blue")
        else:
            messagebox.showerror("Error", "No words found with the selected criteria")
    
    def guess_letter(self):
        """Process letter guess"""
        if not self.current_player:
            return
        
        letter = self.letter_var.get().strip()
        if not letter:
            return
        
        result = self.game.make_guess(letter)
        self.letter_var.set("")
        
        self.update_display()
        self.show_guess_result(result)
        
        if self.game.game_over:
            self.game.save_game_result(self.current_player['id'])
            self.update_player_info()
    
    def guess_word(self):
        """Process word guess"""
        if not self.current_player:
            return
        
        word = self.word_var.get().strip()
        if not word:
            return
        
        result = self.game.guess_word(word)
        self.word_var.set("")
        
        self.update_display()
        self.show_guess_result(result)
        
        if self.game.game_over:
            self.game.save_game_result(self.current_player['id'])
            self.update_player_info()
    
    def show_hint(self):
        """Show hint for current word"""
        if self.game.current_word_data and self.game.current_word_data['hint']:
            hint = self.game.current_word_data['hint']
            messagebox.showinfo("Hint", f"Hint: {hint}")
        else:
            messagebox.showinfo("Hint", "No hint available for this word")
    
    def on_letter_keyrelease(self, event):
        """Handle letter entry key release"""
        # Limit to single character and convert to uppercase
        text = self.letter_var.get().upper()
        if len(text) > 1:
            self.letter_var.set(text[-1])
        elif text and not text.isalpha():
            self.letter_var.set("")
    
    def update_display(self):
        """Update game display"""
        # Update hangman drawing
        self.hangman_text.config(state=tk.NORMAL)
        self.hangman_text.delete(1.0, tk.END)
        self.hangman_text.insert(tk.END, self.game.get_hangman_display())
        self.hangman_text.config(state=tk.DISABLED)
        
        # Update word display
        if self.game.current_word:
            self.word_label.config(text=self.game.get_display_word())
            
            # Update info
            category = self.game.current_word_data['category'].title()
            difficulty = self.game.current_word_data['difficulty']
            self.info_label.config(text=f"Category: {category} | Difficulty: {difficulty}")
        else:
            self.word_label.config(text="Click 'New Game' to start")
            self.info_label.config(text="")
        
        # Update guessed letters
        if self.game.guessed_letters:
            guessed = ", ".join(sorted(self.game.guessed_letters))
            self.guessed_label.config(text=f"Guessed letters: {guessed}")
        else:
            self.guessed_label.config(text="Guessed letters: ")
    
    def show_guess_result(self, result):
        """Show result of guess"""
        status = result['status']
        message = result['message']
        
        if status == 'won':
            self.status_label.config(text=message, foreground="green")
        elif status == 'lost':
            self.status_label.config(text=message, foreground="red")
        elif status == 'correct':
            self.status_label.config(text=message, foreground="blue")
        elif status == 'wrong':
            self.status_label.config(text=message, foreground="orange")
        else:
            self.status_label.config(text=message, foreground="gray")
    
    def update_player_info(self):
        """Update player info after game"""
        if self.current_player:
            # Refresh player stats
            updated_player = self.db.authenticate_player(
                self.current_player['username'], 
                ""  # We'll need to modify this to not require password for refresh
            )
            if updated_player:
                self.current_player = updated_player
                self.player_label.config(text=f"Player: {updated_player['username']} | Wins: {updated_player['total_wins']} | Streak: {updated_player['current_streak']}")
    
    def add_word(self):
        """Add new word to database"""
        word = self.new_word_var.get().strip()
        category = self.new_category_var.get().strip()
        hint = self.new_hint_var.get().strip()
        
        try:
            difficulty = int(self.new_difficulty_var.get())
        except ValueError:
            messagebox.showerror("Error", "Difficulty must be a number between 1 and 5")
            return
        
        if not word or not category:
            messagebox.showerror("Error", "Please enter both word and category")
            return
        
        if not word.replace(" ", "").isalpha():
            messagebox.showerror("Error", "Word should contain only letters (spaces will be removed)")
            return
        
        # Remove spaces from word
        word = word.replace(" ", "")
        
        if self.db.add_word(word, category, difficulty, hint):
            messagebox.showinfo("Success", f"Word '{word}' added successfully!")
            
            # Clear form
            self.new_word_var.set("")
            self.new_category_var.set("")
            self.new_difficulty_var.set("1")
            self.new_hint_var.set("")
            
            # Update categories
            self.update_categories()
        else:
            messagebox.showerror("Error", "Word already exists in database")
    
    def update_stats(self):
        """Update statistics display"""
        if not self.current_player:
            return
        
        stats = self.db.get_player_stats(self.current_player['id'])
        
        self.stats_text.config(state=tk.NORMAL)
        self.stats_text.delete(1.0, tk.END)
        
        # Basic stats
        self.stats_text.insert(tk.END, f"=== Statistics for {stats['username']} ===\n\n")
        self.stats_text.insert(tk.END, f"Member since: {stats['member_since'][:10]}\n")
        self.stats_text.insert(tk.END, f"Total games: {stats['total_games']}\n")
        self.stats_text.insert(tk.END, f"Total wins: {stats['total_wins']}\n")
        self.stats_text.insert(tk.END, f"Total losses: {stats['total_losses']}\n")
        self.stats_text.insert(tk.END, f"Win rate: {stats['win_rate']:.1f}%\n")
        self.stats_text.insert(tk.END, f"Best streak: {stats['best_streak']}\n")
        self.stats_text.insert(tk.END, f"Current streak: {stats['current_streak']}\n\n")
        
        # Category performance
        if stats['category_stats']:
            self.stats_text.insert(tk.END, "=== Performance by Category ===\n")
            for category, games, wins in stats['category_stats']:
                win_rate = (wins / games * 100) if games > 0 else 0
                self.stats_text.insert(tk.END, f"{category.title()}: {wins}/{games} ({win_rate:.1f}%)\n")
            self.stats_text.insert(tk.END, "\n")
        
        # Recent games
        if stats['recent_games']:
            self.stats_text.insert(tk.END, "=== Recent Games ===\n")
            for won, duration, date, word, category in stats['recent_games']:
                result = "Won" if won else "Lost"
                duration_str = f"{duration}s" if duration else "N/A"
                date_str = date[:10]  # Just the date part
                self.stats_text.insert(tk.END, f"{date_str}: {result} - {word.upper()} ({category}) - {duration_str}\n")
        
        self.stats_text.config(state=tk.DISABLED)
    
    def update_leaderboard(self):
        """Update leaderboard display"""
        leaderboard = self.db.get_leaderboard(20)
        
        self.leaderboard_text.config(state=tk.NORMAL)
        self.leaderboard_text.delete(1.0, tk.END)
        
        self.leaderboard_text.insert(tk.END, "=== LEADERBOARD ===\n\n")
        self.leaderboard_text.insert(tk.END, f"{'Rank':<5}{'Player':<15}{'Wins':<6}{'Games':<7}{'Win%':<6}{'Streak':<8}\n")
        self.leaderboard_text.insert(tk.END, "-" * 60 + "\n")
        
        for i, player in enumerate(leaderboard, 1):
            rank = f"{i}."
            username = player['username'][:14]
            wins = str(player['total_wins'])
            games = str(player['total_games'])
            win_rate = f"{player['win_rate']}%"
            streak = str(player['best_streak'])
            
            line = f"{rank:<5}{username:<15}{wins:<6}{games:<7}{win_rate:<6}{streak:<8}\n"
            self.leaderboard_text.insert(tk.END, line)
        
        self.leaderboard_text.config(state=tk.DISABLED)
    
    def run(self):
        """Run the application"""
        self.root.mainloop()
 
def main():
    """Main function"""
    app = HangmanGUI()
    app.run()
 
if __name__ == "__main__":
    main()
 
  1. Save the file.
  2. Run the following command to run the application.
command
C:\Users\username\Documents\hangmanDatabase> python hangmandatabase.py
Hangman Game Database
====================
✓ Database initialized
Default words loaded
✓ User authentication ready
✓ Statistics tracking enabled
Welcome to Advanced Hangman!
Please login or create account...
command
C:\Users\username\Documents\hangmanDatabase> python hangmandatabase.py
Hangman Game Database
====================
✓ Database initialized
Default words loaded
✓ User authentication ready
✓ Statistics tracking enabled
Welcome to Advanced Hangman!
Please login or create account...

Explanation

  1. The HangmanDatabaseHangmanDatabase class manages SQLite database operations for users, words, and game history.
  2. The HangmanGameHangmanGame class handles game logic, state management, and scoring system.
  3. The HangmanGUIHangmanGUI class provides a comprehensive Tkinter interface with multiple tabs.
  4. User authentication system with password hashing for security.
  5. Word management with categories, difficulty levels, and hints.
  6. Statistics tracking including games played, win rate, and performance metrics.
  7. Leaderboard system with ranking based on various criteria.
  8. Game state persistence allowing resume functionality.
  9. Multiple difficulty levels affecting word selection and scoring.
  10. Comprehensive admin panel for word and user management.
  11. Visual hangman drawing with ASCII art progression.
  12. Detailed analytics and reporting for player performance.

Next Steps

Congratulations! You have successfully created a Hangman Game with Database in Python. Experiment with the code and see if you can modify the application. Here are a few suggestions:

  • Add multiplayer functionality with turn-based gameplay
  • Implement themed word categories (animals, countries, movies)
  • Create achievement system with badges and rewards
  • Add sound effects and background music
  • Implement word definition display using dictionary API
  • Create tournament mode with bracket-style competitions
  • Add social features like friend lists and challenges
  • Implement word contribution system for community words
  • Create mobile-responsive web version using Flask

Conclusion

In this project, you learned how to create a Hangman Game with Database in Python using advanced game development concepts. You also learned about database integration, user authentication, statistics tracking, and implementing comprehensive gaming systems with persistent data storage. You can find the source code on GitHub

How It Works

1. Database Schema Design

hangmandatabase.py
class HangmanDatabase:
    def init_database(self):
        with sqlite3.connect(self.db_path) as conn:
            # Users table
            conn.execute('''
                CREATE TABLE IF NOT EXISTS users (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    username TEXT UNIQUE NOT NULL,
                    password_hash TEXT NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    last_login TIMESTAMP
                )
            ''')
            
            # Words table
            conn.execute('''
                CREATE TABLE IF NOT EXISTS words (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    word TEXT NOT NULL,
                    category TEXT NOT NULL,
                    difficulty INTEGER NOT NULL,
                    hint TEXT,
                    added_by INTEGER,
                    FOREIGN KEY (added_by) REFERENCES users (id)
                )
            ''')
hangmandatabase.py
class HangmanDatabase:
    def init_database(self):
        with sqlite3.connect(self.db_path) as conn:
            # Users table
            conn.execute('''
                CREATE TABLE IF NOT EXISTS users (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    username TEXT UNIQUE NOT NULL,
                    password_hash TEXT NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    last_login TIMESTAMP
                )
            ''')
            
            # Words table
            conn.execute('''
                CREATE TABLE IF NOT EXISTS words (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    word TEXT NOT NULL,
                    category TEXT NOT NULL,
                    difficulty INTEGER NOT NULL,
                    hint TEXT,
                    added_by INTEGER,
                    FOREIGN KEY (added_by) REFERENCES users (id)
                )
            ''')

The database structure includes:

  • User Management: Account creation, authentication, and session tracking
  • Word Library: Categorized words with difficulty ratings and hints
  • Game History: Complete record of all games played
  • Statistics: Aggregated player performance metrics
  • Leaderboards: Ranking system for competitive play

2. Authentication System

hangmandatabase.py
def create_player(self, username: str, password: str) -> bool:
    password_hash = hashlib.sha256(password.encode()).hexdigest()
    
    with sqlite3.connect(self.db_path) as conn:
        try:
            conn.execute(
                "INSERT INTO users (username, password_hash) VALUES (?, ?)",
                (username, password_hash)
            )
            return True
        except sqlite3.IntegrityError:
            return False
 
def authenticate_player(self, username: str, password: str) -> Optional[Dict]:
    password_hash = hashlib.sha256(password.encode()).hexdigest()
    
    with sqlite3.connect(self.db_path) as conn:
        cursor = conn.execute(
            "SELECT id, username FROM users WHERE username = ? AND password_hash = ?",
            (username, password_hash)
        )
        user = cursor.fetchone()
        return dict(zip([col[0] for col in cursor.description], user)) if user else None
hangmandatabase.py
def create_player(self, username: str, password: str) -> bool:
    password_hash = hashlib.sha256(password.encode()).hexdigest()
    
    with sqlite3.connect(self.db_path) as conn:
        try:
            conn.execute(
                "INSERT INTO users (username, password_hash) VALUES (?, ?)",
                (username, password_hash)
            )
            return True
        except sqlite3.IntegrityError:
            return False
 
def authenticate_player(self, username: str, password: str) -> Optional[Dict]:
    password_hash = hashlib.sha256(password.encode()).hexdigest()
    
    with sqlite3.connect(self.db_path) as conn:
        cursor = conn.execute(
            "SELECT id, username FROM users WHERE username = ? AND password_hash = ?",
            (username, password_hash)
        )
        user = cursor.fetchone()
        return dict(zip([col[0] for col in cursor.description], user)) if user else None

3. Game Logic Implementation

hangmandatabase.py
class HangmanGame:
    def make_guess(self, letter: str) -> Dict:
        letter = letter.upper()
        
        if self.game_over:
            return {"status": "error", "message": "Game is over"}
        
        if letter in self.guessed_letters:
            return {"status": "error", "message": "Letter already guessed"}
        
        if not letter.isalpha() or len(letter) != 1:
            return {"status": "error", "message": "Please enter a single letter"}
        
        self.guessed_letters.append(letter)
        
        if letter in self.current_word:
            correct_positions = [i for i, char in enumerate(self.current_word) if char == letter]
            
            if self.is_word_complete():
                self.game_won = True
                self.game_over = True
                self.end_time = datetime.datetime.now()
                return {
                    "status": "won",
                    "message": f"Congratulations! You won! The word was: {self.current_word}",
                    "letter": letter,
                    "positions": correct_positions
                }
hangmandatabase.py
class HangmanGame:
    def make_guess(self, letter: str) -> Dict:
        letter = letter.upper()
        
        if self.game_over:
            return {"status": "error", "message": "Game is over"}
        
        if letter in self.guessed_letters:
            return {"status": "error", "message": "Letter already guessed"}
        
        if not letter.isalpha() or len(letter) != 1:
            return {"status": "error", "message": "Please enter a single letter"}
        
        self.guessed_letters.append(letter)
        
        if letter in self.current_word:
            correct_positions = [i for i, char in enumerate(self.current_word) if char == letter]
            
            if self.is_word_complete():
                self.game_won = True
                self.game_over = True
                self.end_time = datetime.datetime.now()
                return {
                    "status": "won",
                    "message": f"Congratulations! You won! The word was: {self.current_word}",
                    "letter": letter,
                    "positions": correct_positions
                }

4. Statistics and Analytics

hangmandatabase.py
def get_player_stats(self, player_id: int) -> Dict:
    with sqlite3.connect(self.db_path) as conn:
        # Basic stats
        cursor = conn.execute(
            "SELECT COUNT(*) as total_games, AVG(CASE WHEN won THEN 1.0 ELSE 0.0 END) as win_rate "
            "FROM game_history WHERE player_id = ?",
            (player_id,)
        )
        basic_stats = cursor.fetchone()
        
        # Category performance
        cursor = conn.execute("""
            SELECT w.category, COUNT(*) as games, 
                   AVG(CASE WHEN gh.won THEN 1.0 ELSE 0.0 END) as win_rate
            FROM game_history gh
            JOIN words w ON gh.word_id = w.id
            WHERE gh.player_id = ?
            GROUP BY w.category
        """, (player_id,))
        category_stats = cursor.fetchall()
hangmandatabase.py
def get_player_stats(self, player_id: int) -> Dict:
    with sqlite3.connect(self.db_path) as conn:
        # Basic stats
        cursor = conn.execute(
            "SELECT COUNT(*) as total_games, AVG(CASE WHEN won THEN 1.0 ELSE 0.0 END) as win_rate "
            "FROM game_history WHERE player_id = ?",
            (player_id,)
        )
        basic_stats = cursor.fetchone()
        
        # Category performance
        cursor = conn.execute("""
            SELECT w.category, COUNT(*) as games, 
                   AVG(CASE WHEN gh.won THEN 1.0 ELSE 0.0 END) as win_rate
            FROM game_history gh
            JOIN words w ON gh.word_id = w.id
            WHERE gh.player_id = ?
            GROUP BY w.category
        """, (player_id,))
        category_stats = cursor.fetchall()

5. GUI Implementation

hangmandatabase.py
class HangmanGUI:
    def setup_ui(self):
        # Create notebook for tabs
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # Game tab
        self.game_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.game_frame, text="Game")
        self.setup_game_tab()
        
        # Statistics tab
        self.stats_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.stats_frame, text="Statistics")
        self.setup_stats_tab()
        
        # Leaderboard tab
        self.leaderboard_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.leaderboard_frame, text="Leaderboard")
        self.setup_leaderboard_tab()
hangmandatabase.py
class HangmanGUI:
    def setup_ui(self):
        # Create notebook for tabs
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # Game tab
        self.game_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.game_frame, text="Game")
        self.setup_game_tab()
        
        # Statistics tab
        self.stats_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.stats_frame, text="Statistics")
        self.setup_stats_tab()
        
        # Leaderboard tab
        self.leaderboard_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.leaderboard_frame, text="Leaderboard")
        self.setup_leaderboard_tab()

6. Hangman Visual Display

hangmandatabase.py
def get_hangman_display(self) -> str:
    hangman_stages = [
        """
   +---+
   |   |
       |
       |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
       |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
   |   |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
  /|   |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
  /|\\  |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
  /|\\  |
  /    |
       |
=========
""",
        """
   +---+
   |   |
   O   |
  /|\\  |
  / \\  |
       |
=========
"""
    ]
    return hangman_stages[self.wrong_guesses]
hangmandatabase.py
def get_hangman_display(self) -> str:
    hangman_stages = [
        """
   +---+
   |   |
       |
       |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
       |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
   |   |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
  /|   |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
  /|\\  |
       |
       |
=========
""",
        """
   +---+
   |   |
   O   |
  /|\\  |
  /    |
       |
=========
""",
        """
   +---+
   |   |
   O   |
  /|\\  |
  / \\  |
       |
=========
"""
    ]
    return hangman_stages[self.wrong_guesses]

Advanced Game Features

1. Multiplayer Tournament System

hangmandatabase.py
class TournamentManager:
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.active_tournaments = {}
    
    def create_tournament(self, name: str, max_players: int = 8, 
                         rounds: int = 3, time_limit: int = 300) -> int:
        """Create a new tournament"""
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS tournaments (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    max_players INTEGER NOT NULL,
                    rounds INTEGER NOT NULL,
                    time_limit INTEGER NOT NULL,
                    status TEXT DEFAULT 'waiting',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    started_at TIMESTAMP,
                    completed_at TIMESTAMP
                )
            ''')
            
            conn.execute('''
                CREATE TABLE IF NOT EXISTS tournament_participants (
                    tournament_id INTEGER,
                    player_id INTEGER,
                    joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    eliminated_round INTEGER,
                    final_rank INTEGER,
                    FOREIGN KEY (tournament_id) REFERENCES tournaments (id),
                    FOREIGN KEY (player_id) REFERENCES users (id)
                )
            ''')
            
            cursor = conn.execute(
                "INSERT INTO tournaments (name, max_players, rounds, time_limit) VALUES (?, ?, ?, ?)",
                (name, max_players, rounds, time_limit)
            )
            tournament_id = cursor.lastrowid
            
            self.active_tournaments[tournament_id] = {
                'name': name,
                'max_players': max_players,
                'participants': [],
                'current_round': 0,
                'status': 'waiting'
            }
            
            return tournament_id
    
    def join_tournament(self, tournament_id: int, player_id: int) -> bool:
        """Join a tournament"""
        tournament = self.active_tournaments.get(tournament_id)
        if not tournament or tournament['status'] != 'waiting':
            return False
        
        if len(tournament['participants']) >= tournament['max_players']:
            return False
        
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute(
                "INSERT INTO tournament_participants (tournament_id, player_id) VALUES (?, ?)",
                (tournament_id, player_id)
            )
        
        tournament['participants'].append(player_id)
        
        # Start tournament if full
        if len(tournament['participants']) == tournament['max_players']:
            self.start_tournament(tournament_id)
        
        return True
    
    def start_tournament(self, tournament_id: int):
        """Start tournament when ready"""
        tournament = self.active_tournaments[tournament_id]
        tournament['status'] = 'active'
        tournament['current_round'] = 1
        
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute(
                "UPDATE tournaments SET status = 'active', started_at = datetime('now') WHERE id = ?",
                (tournament_id,)
            )
        
        # Initialize first round
        self.create_tournament_round(tournament_id, 1)
    
    def create_tournament_round(self, tournament_id: int, round_number: int):
        """Create games for tournament round"""
        tournament = self.active_tournaments[tournament_id]
        participants = tournament['participants']
        
        # Create matchups (for now, all players play same word)
        word_data = self.db.get_random_word(difficulty=round_number)
        
        for player_id in participants:
            # Create game record
            with sqlite3.connect(self.db.db_path) as conn:
                conn.execute('''
                    INSERT INTO tournament_games 
                    (tournament_id, round_number, player_id, word_id, status)
                    VALUES (?, ?, ?, ?, 'pending')
                ''', (tournament_id, round_number, player_id, word_data['id']))
hangmandatabase.py
class TournamentManager:
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.active_tournaments = {}
    
    def create_tournament(self, name: str, max_players: int = 8, 
                         rounds: int = 3, time_limit: int = 300) -> int:
        """Create a new tournament"""
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS tournaments (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    max_players INTEGER NOT NULL,
                    rounds INTEGER NOT NULL,
                    time_limit INTEGER NOT NULL,
                    status TEXT DEFAULT 'waiting',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    started_at TIMESTAMP,
                    completed_at TIMESTAMP
                )
            ''')
            
            conn.execute('''
                CREATE TABLE IF NOT EXISTS tournament_participants (
                    tournament_id INTEGER,
                    player_id INTEGER,
                    joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    eliminated_round INTEGER,
                    final_rank INTEGER,
                    FOREIGN KEY (tournament_id) REFERENCES tournaments (id),
                    FOREIGN KEY (player_id) REFERENCES users (id)
                )
            ''')
            
            cursor = conn.execute(
                "INSERT INTO tournaments (name, max_players, rounds, time_limit) VALUES (?, ?, ?, ?)",
                (name, max_players, rounds, time_limit)
            )
            tournament_id = cursor.lastrowid
            
            self.active_tournaments[tournament_id] = {
                'name': name,
                'max_players': max_players,
                'participants': [],
                'current_round': 0,
                'status': 'waiting'
            }
            
            return tournament_id
    
    def join_tournament(self, tournament_id: int, player_id: int) -> bool:
        """Join a tournament"""
        tournament = self.active_tournaments.get(tournament_id)
        if not tournament or tournament['status'] != 'waiting':
            return False
        
        if len(tournament['participants']) >= tournament['max_players']:
            return False
        
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute(
                "INSERT INTO tournament_participants (tournament_id, player_id) VALUES (?, ?)",
                (tournament_id, player_id)
            )
        
        tournament['participants'].append(player_id)
        
        # Start tournament if full
        if len(tournament['participants']) == tournament['max_players']:
            self.start_tournament(tournament_id)
        
        return True
    
    def start_tournament(self, tournament_id: int):
        """Start tournament when ready"""
        tournament = self.active_tournaments[tournament_id]
        tournament['status'] = 'active'
        tournament['current_round'] = 1
        
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute(
                "UPDATE tournaments SET status = 'active', started_at = datetime('now') WHERE id = ?",
                (tournament_id,)
            )
        
        # Initialize first round
        self.create_tournament_round(tournament_id, 1)
    
    def create_tournament_round(self, tournament_id: int, round_number: int):
        """Create games for tournament round"""
        tournament = self.active_tournaments[tournament_id]
        participants = tournament['participants']
        
        # Create matchups (for now, all players play same word)
        word_data = self.db.get_random_word(difficulty=round_number)
        
        for player_id in participants:
            # Create game record
            with sqlite3.connect(self.db.db_path) as conn:
                conn.execute('''
                    INSERT INTO tournament_games 
                    (tournament_id, round_number, player_id, word_id, status)
                    VALUES (?, ?, ?, ?, 'pending')
                ''', (tournament_id, round_number, player_id, word_data['id']))

2. Achievement System

hangmandatabase.py
class AchievementSystem:
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.achievements = self.load_achievements()
    
    def load_achievements(self) -> Dict:
        """Load achievement definitions"""
        return {
            'first_win': {
                'name': 'First Victory',
                'description': 'Win your first game',
                'icon': '🎉',
                'points': 10
            },
            'perfect_game': {
                'name': 'Perfect Game',
                'description': 'Win without any wrong guesses',
                'icon': '⭐',
                'points': 50
            },
            'speed_demon': {
                'name': 'Speed Demon',
                'description': 'Complete a game in under 30 seconds',
                'icon': '⚡',
                'points': 30
            },
            'word_master': {
                'name': 'Word Master',
                'description': 'Win 100 games',
                'icon': '👑',
                'points': 100
            },
            'streak_master': {
                'name': 'Streak Master',
                'description': 'Win 10 games in a row',
                'icon': '🔥',
                'points': 75
            },
            'category_expert': {
                'name': 'Category Expert',
                'description': 'Win 20 games in a single category',
                'icon': '🎯',
                'points': 40
            },
            'hint_free': {
                'name': 'Hint Free',
                'description': 'Win 5 games without using hints',
                'icon': '🧠',
                'points': 25
            }
        }
    
    def check_achievements(self, player_id: int, game_result: Dict):
        """Check and award achievements"""
        new_achievements = []
        
        # Get player stats
        stats = self.db.get_player_stats(player_id)
        
        # Check each achievement
        for achievement_id, achievement in self.achievements.items():
            if self.has_achievement(player_id, achievement_id):
                continue  # Already earned
            
            if self.check_achievement_condition(achievement_id, stats, game_result):
                self.award_achievement(player_id, achievement_id)
                new_achievements.append(achievement)
        
        return new_achievements
    
    def check_achievement_condition(self, achievement_id: str, stats: Dict, game_result: Dict) -> bool:
        """Check if achievement condition is met"""
        if achievement_id == 'first_win':
            return stats['total_games_won'] == 1
        
        elif achievement_id == 'perfect_game':
            return game_result['won'] and game_result['wrong_guesses'] == 0
        
        elif achievement_id == 'speed_demon':
            return game_result['won'] and game_result['duration'] < 30
        
        elif achievement_id == 'word_master':
            return stats['total_games_won'] >= 100
        
        elif achievement_id == 'streak_master':
            return stats.get('current_win_streak', 0) >= 10
        
        elif achievement_id == 'category_expert':
            # Check if won 20+ games in any single category
            for category_stat in stats.get('category_performance', []):
                if category_stat['games_won'] >= 20:
                    return True
        
        elif achievement_id == 'hint_free':
            # Check last 5 games for hint usage
            return self.check_recent_hint_free_games(stats['player_id'], 5)
        
        return False
    
    def award_achievement(self, player_id: int, achievement_id: str):
        """Award achievement to player"""
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS player_achievements (
                    player_id INTEGER,
                    achievement_id TEXT,
                    earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (player_id) REFERENCES users (id)
                )
            ''')
            
            conn.execute(
                "INSERT INTO player_achievements (player_id, achievement_id) VALUES (?, ?)",
                (player_id, achievement_id)
            )
            
            # Award points
            achievement = self.achievements[achievement_id]
            conn.execute(
                "UPDATE users SET achievement_points = achievement_points + ? WHERE id = ?",
                (achievement['points'], player_id)
            )
hangmandatabase.py
class AchievementSystem:
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.achievements = self.load_achievements()
    
    def load_achievements(self) -> Dict:
        """Load achievement definitions"""
        return {
            'first_win': {
                'name': 'First Victory',
                'description': 'Win your first game',
                'icon': '🎉',
                'points': 10
            },
            'perfect_game': {
                'name': 'Perfect Game',
                'description': 'Win without any wrong guesses',
                'icon': '⭐',
                'points': 50
            },
            'speed_demon': {
                'name': 'Speed Demon',
                'description': 'Complete a game in under 30 seconds',
                'icon': '⚡',
                'points': 30
            },
            'word_master': {
                'name': 'Word Master',
                'description': 'Win 100 games',
                'icon': '👑',
                'points': 100
            },
            'streak_master': {
                'name': 'Streak Master',
                'description': 'Win 10 games in a row',
                'icon': '🔥',
                'points': 75
            },
            'category_expert': {
                'name': 'Category Expert',
                'description': 'Win 20 games in a single category',
                'icon': '🎯',
                'points': 40
            },
            'hint_free': {
                'name': 'Hint Free',
                'description': 'Win 5 games without using hints',
                'icon': '🧠',
                'points': 25
            }
        }
    
    def check_achievements(self, player_id: int, game_result: Dict):
        """Check and award achievements"""
        new_achievements = []
        
        # Get player stats
        stats = self.db.get_player_stats(player_id)
        
        # Check each achievement
        for achievement_id, achievement in self.achievements.items():
            if self.has_achievement(player_id, achievement_id):
                continue  # Already earned
            
            if self.check_achievement_condition(achievement_id, stats, game_result):
                self.award_achievement(player_id, achievement_id)
                new_achievements.append(achievement)
        
        return new_achievements
    
    def check_achievement_condition(self, achievement_id: str, stats: Dict, game_result: Dict) -> bool:
        """Check if achievement condition is met"""
        if achievement_id == 'first_win':
            return stats['total_games_won'] == 1
        
        elif achievement_id == 'perfect_game':
            return game_result['won'] and game_result['wrong_guesses'] == 0
        
        elif achievement_id == 'speed_demon':
            return game_result['won'] and game_result['duration'] < 30
        
        elif achievement_id == 'word_master':
            return stats['total_games_won'] >= 100
        
        elif achievement_id == 'streak_master':
            return stats.get('current_win_streak', 0) >= 10
        
        elif achievement_id == 'category_expert':
            # Check if won 20+ games in any single category
            for category_stat in stats.get('category_performance', []):
                if category_stat['games_won'] >= 20:
                    return True
        
        elif achievement_id == 'hint_free':
            # Check last 5 games for hint usage
            return self.check_recent_hint_free_games(stats['player_id'], 5)
        
        return False
    
    def award_achievement(self, player_id: int, achievement_id: str):
        """Award achievement to player"""
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS player_achievements (
                    player_id INTEGER,
                    achievement_id TEXT,
                    earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (player_id) REFERENCES users (id)
                )
            ''')
            
            conn.execute(
                "INSERT INTO player_achievements (player_id, achievement_id) VALUES (?, ?)",
                (player_id, achievement_id)
            )
            
            # Award points
            achievement = self.achievements[achievement_id]
            conn.execute(
                "UPDATE users SET achievement_points = achievement_points + ? WHERE id = ?",
                (achievement['points'], player_id)
            )

3. Advanced Word Management

hangmandatabase.py
def import_word_list(self, file_path: str, category: str = "imported") -> Dict:
    """Import words from external file"""
    results = {'imported': 0, 'skipped': 0, 'errors': []}
    
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            for line_num, line in enumerate(f, 1):
                line = line.strip().upper()
                
                # Skip empty lines and comments
                if not line or line.startswith('#'):
                    continue
                
                # Parse line (word, difficulty, hint)
                parts = line.split('|')
                word = parts[0].strip()
                difficulty = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 2
                hint = parts[2].strip() if len(parts) > 2 else ""
                
                # Validate word
                if not word.isalpha() or len(word) < 3:
                    results['errors'].append(f"Line {line_num}: Invalid word '{word}'")
                    continue
                
                # Check if word exists
                if self.word_exists(word):
                    results['skipped'] += 1
                    continue
                
                # Add word
                if self.add_word(word, category, difficulty, hint):
                    results['imported'] += 1
                else:
                    results['errors'].append(f"Line {line_num}: Failed to add word '{word}'")
    
    except Exception as e:
        results['errors'].append(f"File error: {str(e)}")
    
    return results
 
def get_word_suggestions(self, partial_word: str, category: str = None) -> List[str]:
    """Get word suggestions based on partial input"""
    with sqlite3.connect(self.db_path) as conn:
        query = "SELECT word FROM words WHERE word LIKE ?"
        params = [f"{partial_word}%"]
        
        if category:
            query += " AND category = ?"
            params.append(category)
        
        query += " ORDER BY LENGTH(word), word LIMIT 10"
        
        cursor = conn.execute(query, params)
        return [row[0] for row in cursor.fetchall()]
 
def analyze_word_difficulty(self, word: str) -> Dict:
    """Analyze word characteristics to suggest difficulty"""
    analysis = {
        'length': len(word),
        'unique_letters': len(set(word)),
        'vowel_count': sum(1 for char in word if char in 'AEIOU'),
        'consonant_clusters': 0,
        'common_letters': 0,
        'suggested_difficulty': 2
    }
    
    # Common letters in English
    common_letters = 'ETAOINSHRDLCUMWFGYPBVKJXQZ'
    analysis['common_letters'] = sum(1 for char in word if char in common_letters[:12])
    
    # Count consonant clusters
    consonants = 'BCDFGHJKLMNPQRSTVWXYZ'
    cluster_count = 0
    for i in range(len(word) - 1):
        if word[i] in consonants and word[i + 1] in consonants:
            cluster_count += 1
    analysis['consonant_clusters'] = cluster_count
    
    # Suggest difficulty based on characteristics
    difficulty_score = 0
    
    if analysis['length'] > 8:
        difficulty_score += 1
    if analysis['unique_letters'] / analysis['length'] > 0.8:
        difficulty_score += 1
    if analysis['consonant_clusters'] > 2:
        difficulty_score += 1
    if analysis['common_letters'] < 3:
        difficulty_score += 1
    
    analysis['suggested_difficulty'] = min(max(1, 2 + difficulty_score), 5)
    
    return analysis
hangmandatabase.py
def import_word_list(self, file_path: str, category: str = "imported") -> Dict:
    """Import words from external file"""
    results = {'imported': 0, 'skipped': 0, 'errors': []}
    
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            for line_num, line in enumerate(f, 1):
                line = line.strip().upper()
                
                # Skip empty lines and comments
                if not line or line.startswith('#'):
                    continue
                
                # Parse line (word, difficulty, hint)
                parts = line.split('|')
                word = parts[0].strip()
                difficulty = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 2
                hint = parts[2].strip() if len(parts) > 2 else ""
                
                # Validate word
                if not word.isalpha() or len(word) < 3:
                    results['errors'].append(f"Line {line_num}: Invalid word '{word}'")
                    continue
                
                # Check if word exists
                if self.word_exists(word):
                    results['skipped'] += 1
                    continue
                
                # Add word
                if self.add_word(word, category, difficulty, hint):
                    results['imported'] += 1
                else:
                    results['errors'].append(f"Line {line_num}: Failed to add word '{word}'")
    
    except Exception as e:
        results['errors'].append(f"File error: {str(e)}")
    
    return results
 
def get_word_suggestions(self, partial_word: str, category: str = None) -> List[str]:
    """Get word suggestions based on partial input"""
    with sqlite3.connect(self.db_path) as conn:
        query = "SELECT word FROM words WHERE word LIKE ?"
        params = [f"{partial_word}%"]
        
        if category:
            query += " AND category = ?"
            params.append(category)
        
        query += " ORDER BY LENGTH(word), word LIMIT 10"
        
        cursor = conn.execute(query, params)
        return [row[0] for row in cursor.fetchall()]
 
def analyze_word_difficulty(self, word: str) -> Dict:
    """Analyze word characteristics to suggest difficulty"""
    analysis = {
        'length': len(word),
        'unique_letters': len(set(word)),
        'vowel_count': sum(1 for char in word if char in 'AEIOU'),
        'consonant_clusters': 0,
        'common_letters': 0,
        'suggested_difficulty': 2
    }
    
    # Common letters in English
    common_letters = 'ETAOINSHRDLCUMWFGYPBVKJXQZ'
    analysis['common_letters'] = sum(1 for char in word if char in common_letters[:12])
    
    # Count consonant clusters
    consonants = 'BCDFGHJKLMNPQRSTVWXYZ'
    cluster_count = 0
    for i in range(len(word) - 1):
        if word[i] in consonants and word[i + 1] in consonants:
            cluster_count += 1
    analysis['consonant_clusters'] = cluster_count
    
    # Suggest difficulty based on characteristics
    difficulty_score = 0
    
    if analysis['length'] > 8:
        difficulty_score += 1
    if analysis['unique_letters'] / analysis['length'] > 0.8:
        difficulty_score += 1
    if analysis['consonant_clusters'] > 2:
        difficulty_score += 1
    if analysis['common_letters'] < 3:
        difficulty_score += 1
    
    analysis['suggested_difficulty'] = min(max(1, 2 + difficulty_score), 5)
    
    return analysis

4. Game Variations and Modes

hangmandatabase.py
class GameModeManager:
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.game_modes = self.initialize_game_modes()
    
    def initialize_game_modes(self) -> Dict:
        """Initialize different game modes"""
        return {
            'classic': {
                'name': 'Classic Hangman',
                'description': 'Traditional hangman game',
                'max_wrong_guesses': 6,
                'hints_allowed': True,
                'time_limit': None,
                'scoring_multiplier': 1.0
            },
            'speed': {
                'name': 'Speed Mode',
                'description': 'Fast-paced with time limit',
                'max_wrong_guesses': 6,
                'hints_allowed': False,
                'time_limit': 60,
                'scoring_multiplier': 1.5
            },
            'hardcore': {
                'name': 'Hardcore',
                'description': 'No hints, fewer guesses',
                'max_wrong_guesses': 4,
                'hints_allowed': False,
                'time_limit': None,
                'scoring_multiplier': 2.0
            },
            'zen': {
                'name': 'Zen Mode',
                'description': 'Unlimited guesses, no pressure',
                'max_wrong_guesses': 999,
                'hints_allowed': True,
                'time_limit': None,
                'scoring_multiplier': 0.5
            },
            'challenge': {
                'name': 'Daily Challenge',
                'description': 'Special daily word with bonus points',
                'max_wrong_guesses': 6,
                'hints_allowed': True,
                'time_limit': None,
                'scoring_multiplier': 3.0
            }
        }
    
    def create_game_with_mode(self, player_id: int, mode: str, category: str = None) -> 'HangmanGame':
        """Create game instance with specific mode"""
        if mode not in self.game_modes:
            raise ValueError(f"Unknown game mode: {mode}")
        
        mode_config = self.game_modes[mode]
        
        # Get appropriate word based on mode
        if mode == 'challenge':
            word_data = self.get_daily_challenge_word()
        else:
            difficulty = self.get_player_appropriate_difficulty(player_id)
            word_data = self.db.get_random_word(category, difficulty)
        
        # Create custom game instance
        game = HangmanGame(self.db)
        game.mode = mode
        game.mode_config = mode_config
        game.max_wrong_guesses = mode_config['max_wrong_guesses']
        game.hints_allowed = mode_config['hints_allowed']
        game.time_limit = mode_config['time_limit']
        game.scoring_multiplier = mode_config['scoring_multiplier']
        
        if word_data:
            game.current_word = word_data['word']
            game.current_word_data = word_data
            game.start_time = datetime.datetime.now()
            
            if game.time_limit:
                game.end_time = game.start_time + datetime.timedelta(seconds=game.time_limit)
        
        return game
    
    def get_daily_challenge_word(self) -> Dict:
        """Get today's daily challenge word"""
        today = datetime.date.today()
        
        with sqlite3.connect(self.db.db_path) as conn:
            # Check if today's challenge exists
            cursor = conn.execute(
                "SELECT * FROM daily_challenges WHERE date = ?",
                (today.isoformat(),)
            )
            challenge = cursor.fetchone()
            
            if challenge:
                # Return existing challenge
                cursor = conn.execute(
                    "SELECT * FROM words WHERE id = ?",
                    (challenge[2],)  # word_id
                )
                return dict(zip([col[0] for col in cursor.description], cursor.fetchone()))
            else:
                # Create new daily challenge
                word_data = self.db.get_random_word(difficulty=3)  # Medium difficulty
                
                conn.execute(
                    "INSERT INTO daily_challenges (date, word_id) VALUES (?, ?)",
                    (today.isoformat(), word_data['id'])
                )
                
                return word_data
hangmandatabase.py
class GameModeManager:
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.game_modes = self.initialize_game_modes()
    
    def initialize_game_modes(self) -> Dict:
        """Initialize different game modes"""
        return {
            'classic': {
                'name': 'Classic Hangman',
                'description': 'Traditional hangman game',
                'max_wrong_guesses': 6,
                'hints_allowed': True,
                'time_limit': None,
                'scoring_multiplier': 1.0
            },
            'speed': {
                'name': 'Speed Mode',
                'description': 'Fast-paced with time limit',
                'max_wrong_guesses': 6,
                'hints_allowed': False,
                'time_limit': 60,
                'scoring_multiplier': 1.5
            },
            'hardcore': {
                'name': 'Hardcore',
                'description': 'No hints, fewer guesses',
                'max_wrong_guesses': 4,
                'hints_allowed': False,
                'time_limit': None,
                'scoring_multiplier': 2.0
            },
            'zen': {
                'name': 'Zen Mode',
                'description': 'Unlimited guesses, no pressure',
                'max_wrong_guesses': 999,
                'hints_allowed': True,
                'time_limit': None,
                'scoring_multiplier': 0.5
            },
            'challenge': {
                'name': 'Daily Challenge',
                'description': 'Special daily word with bonus points',
                'max_wrong_guesses': 6,
                'hints_allowed': True,
                'time_limit': None,
                'scoring_multiplier': 3.0
            }
        }
    
    def create_game_with_mode(self, player_id: int, mode: str, category: str = None) -> 'HangmanGame':
        """Create game instance with specific mode"""
        if mode not in self.game_modes:
            raise ValueError(f"Unknown game mode: {mode}")
        
        mode_config = self.game_modes[mode]
        
        # Get appropriate word based on mode
        if mode == 'challenge':
            word_data = self.get_daily_challenge_word()
        else:
            difficulty = self.get_player_appropriate_difficulty(player_id)
            word_data = self.db.get_random_word(category, difficulty)
        
        # Create custom game instance
        game = HangmanGame(self.db)
        game.mode = mode
        game.mode_config = mode_config
        game.max_wrong_guesses = mode_config['max_wrong_guesses']
        game.hints_allowed = mode_config['hints_allowed']
        game.time_limit = mode_config['time_limit']
        game.scoring_multiplier = mode_config['scoring_multiplier']
        
        if word_data:
            game.current_word = word_data['word']
            game.current_word_data = word_data
            game.start_time = datetime.datetime.now()
            
            if game.time_limit:
                game.end_time = game.start_time + datetime.timedelta(seconds=game.time_limit)
        
        return game
    
    def get_daily_challenge_word(self) -> Dict:
        """Get today's daily challenge word"""
        today = datetime.date.today()
        
        with sqlite3.connect(self.db.db_path) as conn:
            # Check if today's challenge exists
            cursor = conn.execute(
                "SELECT * FROM daily_challenges WHERE date = ?",
                (today.isoformat(),)
            )
            challenge = cursor.fetchone()
            
            if challenge:
                # Return existing challenge
                cursor = conn.execute(
                    "SELECT * FROM words WHERE id = ?",
                    (challenge[2],)  # word_id
                )
                return dict(zip([col[0] for col in cursor.description], cursor.fetchone()))
            else:
                # Create new daily challenge
                word_data = self.db.get_random_word(difficulty=3)  # Medium difficulty
                
                conn.execute(
                    "INSERT INTO daily_challenges (date, word_id) VALUES (?, ?)",
                    (today.isoformat(), word_data['id'])
                )
                
                return word_data

5. Enhanced Statistics and Analytics

hangmandatabase.py
def get_comprehensive_analytics(self, player_id: int) -> Dict:
    """Get comprehensive player analytics"""
    with sqlite3.connect(self.db_path) as conn:
        analytics = {}
        
        # Performance over time
        cursor = conn.execute("""
            SELECT date(created_at) as game_date,
                   COUNT(*) as games_played,
                   COUNT(CASE WHEN won THEN 1 END) as games_won,
                   AVG(duration) as avg_duration,
                   AVG(wrong_guesses) as avg_wrong_guesses
            FROM game_history 
            WHERE player_id = ? AND created_at >= date('now', '-30 days')
            GROUP BY date(created_at)
            ORDER BY game_date
        """, (player_id,))
        
        analytics['daily_performance'] = [
            {
                'date': row[0],
                'games_played': row[1],
                'games_won': row[2],
                'win_rate': (row[2] / row[1] * 100) if row[1] > 0 else 0,
                'avg_duration': row[3] or 0,
                'avg_wrong_guesses': row[4] or 0
            }
            for row in cursor.fetchall()
        ]
        
        # Difficulty progression
        cursor = conn.execute("""
            SELECT w.difficulty,
                   COUNT(*) as games_played,
                   COUNT(CASE WHEN gh.won THEN 1 END) as games_won,
                   AVG(gh.duration) as avg_duration
            FROM game_history gh
            JOIN words w ON gh.word_id = w.id
            WHERE gh.player_id = ?
            GROUP BY w.difficulty
            ORDER BY w.difficulty
        """, (player_id,))
        
        analytics['difficulty_performance'] = [
            {
                'difficulty': row[0],
                'games_played': row[1],
                'games_won': row[2],
                'win_rate': (row[2] / row[1] * 100) if row[1] > 0 else 0,
                'avg_duration': row[3] or 0
            }
            for row in cursor.fetchall()
        ]
        
        # Letter accuracy
        cursor = conn.execute("""
            SELECT guessed_letters, wrong_guesses
            FROM game_history 
            WHERE player_id = ? AND guessed_letters IS NOT NULL
        """, (player_id,))
        
        letter_stats = {}
        for row in cursor.fetchall():
            letters = eval(row[0]) if row[0] else []
            wrong_count = row[1]
            
            # This is simplified - in real implementation, track correct vs wrong letters
            for letter in letters:
                if letter not in letter_stats:
                    letter_stats[letter] = {'used': 0, 'success_rate': 0}
                letter_stats[letter]['used'] += 1
        
        analytics['letter_usage'] = letter_stats
        
        # Streak analysis
        cursor = conn.execute("""
            SELECT won, created_at
            FROM game_history 
            WHERE player_id = ?
            ORDER BY created_at DESC
            LIMIT 50
        """, (player_id,))
        
        games = cursor.fetchall()
        current_streak = 0
        max_streak = 0
        temp_streak = 0
        
        for game in games:
            if game[0]:  # Won
                temp_streak += 1
                max_streak = max(max_streak, temp_streak)
                if current_streak == 0:  # First win in sequence
                    current_streak = temp_streak
            else:  # Lost
                temp_streak = 0
        
        analytics['streaks'] = {
            'current_win_streak': current_streak,
            'best_win_streak': max_streak
        }
        
        # Time-based patterns
        cursor = conn.execute("""
            SELECT strftime('%H', created_at) as hour,
                   COUNT(*) as games_played,
                   COUNT(CASE WHEN won THEN 1 END) as games_won
            FROM game_history 
            WHERE player_id = ?
            GROUP BY strftime('%H', created_at)
            ORDER BY hour
        """, (player_id,))
        
        analytics['hourly_performance'] = [
            {
                'hour': int(row[0]),
                'games_played': row[1],
                'games_won': row[2],
                'win_rate': (row[2] / row[1] * 100) if row[1] > 0 else 0
            }
            for row in cursor.fetchall()
        ]
        
        return analytics
 
def generate_progress_report(self, player_id: int) -> str:
    """Generate detailed progress report"""
    analytics = self.get_comprehensive_analytics(player_id)
    stats = self.get_player_stats(player_id)
    
    report = f"""
=== HANGMAN PROGRESS REPORT ===
 
Player Statistics:
- Total Games: {stats['total_games']}
- Games Won: {stats['total_games_won']}
- Win Rate: {stats['win_rate']:.1f}%
- Average Duration: {stats['avg_duration']:.1f} seconds
- Current Streak: {analytics['streaks']['current_win_streak']}
- Best Streak: {analytics['streaks']['best_win_streak']}
 
Difficulty Breakdown:
"""
    
    for diff_stat in analytics['difficulty_performance']:
        report += f"- Level {diff_stat['difficulty']}: {diff_stat['win_rate']:.1f}% win rate ({diff_stat['games_won']}/{diff_stat['games_played']})\n"
    
    report += f"""
Recent Performance (Last 7 Days):
"""
    recent_games = analytics['daily_performance'][-7:]
    total_recent = sum(day['games_played'] for day in recent_games)
    won_recent = sum(day['games_won'] for day in recent_games)
    
    report += f"- Games Played: {total_recent}\n"
    report += f"- Games Won: {won_recent}\n"
    report += f"- Recent Win Rate: {(won_recent/total_recent*100) if total_recent > 0 else 0:.1f}%\n"
    
    report += "\n=== END REPORT ===\n"
    
    return report
hangmandatabase.py
def get_comprehensive_analytics(self, player_id: int) -> Dict:
    """Get comprehensive player analytics"""
    with sqlite3.connect(self.db_path) as conn:
        analytics = {}
        
        # Performance over time
        cursor = conn.execute("""
            SELECT date(created_at) as game_date,
                   COUNT(*) as games_played,
                   COUNT(CASE WHEN won THEN 1 END) as games_won,
                   AVG(duration) as avg_duration,
                   AVG(wrong_guesses) as avg_wrong_guesses
            FROM game_history 
            WHERE player_id = ? AND created_at >= date('now', '-30 days')
            GROUP BY date(created_at)
            ORDER BY game_date
        """, (player_id,))
        
        analytics['daily_performance'] = [
            {
                'date': row[0],
                'games_played': row[1],
                'games_won': row[2],
                'win_rate': (row[2] / row[1] * 100) if row[1] > 0 else 0,
                'avg_duration': row[3] or 0,
                'avg_wrong_guesses': row[4] or 0
            }
            for row in cursor.fetchall()
        ]
        
        # Difficulty progression
        cursor = conn.execute("""
            SELECT w.difficulty,
                   COUNT(*) as games_played,
                   COUNT(CASE WHEN gh.won THEN 1 END) as games_won,
                   AVG(gh.duration) as avg_duration
            FROM game_history gh
            JOIN words w ON gh.word_id = w.id
            WHERE gh.player_id = ?
            GROUP BY w.difficulty
            ORDER BY w.difficulty
        """, (player_id,))
        
        analytics['difficulty_performance'] = [
            {
                'difficulty': row[0],
                'games_played': row[1],
                'games_won': row[2],
                'win_rate': (row[2] / row[1] * 100) if row[1] > 0 else 0,
                'avg_duration': row[3] or 0
            }
            for row in cursor.fetchall()
        ]
        
        # Letter accuracy
        cursor = conn.execute("""
            SELECT guessed_letters, wrong_guesses
            FROM game_history 
            WHERE player_id = ? AND guessed_letters IS NOT NULL
        """, (player_id,))
        
        letter_stats = {}
        for row in cursor.fetchall():
            letters = eval(row[0]) if row[0] else []
            wrong_count = row[1]
            
            # This is simplified - in real implementation, track correct vs wrong letters
            for letter in letters:
                if letter not in letter_stats:
                    letter_stats[letter] = {'used': 0, 'success_rate': 0}
                letter_stats[letter]['used'] += 1
        
        analytics['letter_usage'] = letter_stats
        
        # Streak analysis
        cursor = conn.execute("""
            SELECT won, created_at
            FROM game_history 
            WHERE player_id = ?
            ORDER BY created_at DESC
            LIMIT 50
        """, (player_id,))
        
        games = cursor.fetchall()
        current_streak = 0
        max_streak = 0
        temp_streak = 0
        
        for game in games:
            if game[0]:  # Won
                temp_streak += 1
                max_streak = max(max_streak, temp_streak)
                if current_streak == 0:  # First win in sequence
                    current_streak = temp_streak
            else:  # Lost
                temp_streak = 0
        
        analytics['streaks'] = {
            'current_win_streak': current_streak,
            'best_win_streak': max_streak
        }
        
        # Time-based patterns
        cursor = conn.execute("""
            SELECT strftime('%H', created_at) as hour,
                   COUNT(*) as games_played,
                   COUNT(CASE WHEN won THEN 1 END) as games_won
            FROM game_history 
            WHERE player_id = ?
            GROUP BY strftime('%H', created_at)
            ORDER BY hour
        """, (player_id,))
        
        analytics['hourly_performance'] = [
            {
                'hour': int(row[0]),
                'games_played': row[1],
                'games_won': row[2],
                'win_rate': (row[2] / row[1] * 100) if row[1] > 0 else 0
            }
            for row in cursor.fetchall()
        ]
        
        return analytics
 
def generate_progress_report(self, player_id: int) -> str:
    """Generate detailed progress report"""
    analytics = self.get_comprehensive_analytics(player_id)
    stats = self.get_player_stats(player_id)
    
    report = f"""
=== HANGMAN PROGRESS REPORT ===
 
Player Statistics:
- Total Games: {stats['total_games']}
- Games Won: {stats['total_games_won']}
- Win Rate: {stats['win_rate']:.1f}%
- Average Duration: {stats['avg_duration']:.1f} seconds
- Current Streak: {analytics['streaks']['current_win_streak']}
- Best Streak: {analytics['streaks']['best_win_streak']}
 
Difficulty Breakdown:
"""
    
    for diff_stat in analytics['difficulty_performance']:
        report += f"- Level {diff_stat['difficulty']}: {diff_stat['win_rate']:.1f}% win rate ({diff_stat['games_won']}/{diff_stat['games_played']})\n"
    
    report += f"""
Recent Performance (Last 7 Days):
"""
    recent_games = analytics['daily_performance'][-7:]
    total_recent = sum(day['games_played'] for day in recent_games)
    won_recent = sum(day['games_won'] for day in recent_games)
    
    report += f"- Games Played: {total_recent}\n"
    report += f"- Games Won: {won_recent}\n"
    report += f"- Recent Win Rate: {(won_recent/total_recent*100) if total_recent > 0 else 0:.1f}%\n"
    
    report += "\n=== END REPORT ===\n"
    
    return report

6. Social Features and Leaderboards

hangmandatabase.py
class SocialFeatures:
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.setup_social_tables()
    
    def setup_social_tables(self):
        """Setup social feature tables"""
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS friendships (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    player1_id INTEGER,
                    player2_id INTEGER,
                    status TEXT DEFAULT 'pending',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (player1_id) REFERENCES users (id),
                    FOREIGN KEY (player2_id) REFERENCES users (id)
                )
            ''')
            
            conn.execute('''
                CREATE TABLE IF NOT EXISTS challenges (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    challenger_id INTEGER,
                    challenged_id INTEGER,
                    word_id INTEGER,
                    challenger_score INTEGER,
                    challenged_score INTEGER,
                    status TEXT DEFAULT 'pending',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    completed_at TIMESTAMP,
                    FOREIGN KEY (challenger_id) REFERENCES users (id),
                    FOREIGN KEY (challenged_id) REFERENCES users (id),
                    FOREIGN KEY (word_id) REFERENCES words (id)
                )
            ''')
    
    def send_friend_request(self, sender_id: int, recipient_username: str) -> bool:
        """Send friend request"""
        with sqlite3.connect(self.db.db_path) as conn:
            # Get recipient ID
            cursor = conn.execute(
                "SELECT id FROM users WHERE username = ?",
                (recipient_username,)
            )
            recipient = cursor.fetchone()
            
            if not recipient:
                return False
            
            recipient_id = recipient[0]
            
            # Check if friendship already exists
            cursor = conn.execute("""
                SELECT id FROM friendships 
                WHERE (player1_id = ? AND player2_id = ?) 
                   OR (player1_id = ? AND player2_id = ?)
            """, (sender_id, recipient_id, recipient_id, sender_id))
            
            if cursor.fetchone():
                return False  # Friendship already exists
            
            # Create friend request
            conn.execute(
                "INSERT INTO friendships (player1_id, player2_id) VALUES (?, ?)",
                (sender_id, recipient_id)
            )
            
            return True
    
    def accept_friend_request(self, friendship_id: int) -> bool:
        """Accept friend request"""
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute(
                "UPDATE friendships SET status = 'accepted' WHERE id = ?",
                (friendship_id,)
            )
            return True
    
    def create_challenge(self, challenger_id: int, challenged_username: str, word_id: int) -> int:
        """Create a challenge between players"""
        with sqlite3.connect(self.db.db_path) as conn:
            # Get challenged player ID
            cursor = conn.execute(
                "SELECT id FROM users WHERE username = ?",
                (challenged_username,)
            )
            challenged = cursor.fetchone()
            
            if not challenged:
                return None
            
            challenged_id = challenged[0]
            
            # Create challenge
            cursor = conn.execute(
                "INSERT INTO challenges (challenger_id, challenged_id, word_id) VALUES (?, ?, ?)",
                (challenger_id, challenged_id, word_id)
            )
            
            return cursor.lastrowid
    
    def get_global_leaderboard(self, period: str = 'all_time', limit: int = 10) -> List[Dict]:
        """Get global leaderboard"""
        with sqlite3.connect(self.db.db_path) as conn:
            base_query = """
                SELECT u.username, u.achievement_points,
                       COUNT(gh.id) as total_games,
                       COUNT(CASE WHEN gh.won THEN 1 END) as games_won,
                       AVG(CASE WHEN gh.won THEN gh.duration END) as avg_win_time
                FROM users u
                LEFT JOIN game_history gh ON u.id = gh.player_id
            """
            
            if period == 'weekly':
                base_query += " WHERE gh.created_at >= date('now', '-7 days')"
            elif period == 'monthly':
                base_query += " WHERE gh.created_at >= date('now', '-30 days')"
            
            base_query += """
                GROUP BY u.id, u.username, u.achievement_points
                HAVING total_games > 0
                ORDER BY games_won DESC, avg_win_time ASC
                LIMIT ?
            """
            
            cursor = conn.execute(base_query, (limit,))
            
            leaderboard = []
            for i, row in enumerate(cursor.fetchall(), 1):
                leaderboard.append({
                    'rank': i,
                    'username': row[0],
                    'achievement_points': row[1],
                    'total_games': row[2],
                    'games_won': row[3],
                    'win_rate': (row[3] / row[2] * 100) if row[2] > 0 else 0,
                    'avg_win_time': row[4] or 0
                })
            
            return leaderboard
hangmandatabase.py
class SocialFeatures:
    def __init__(self, database: HangmanDatabase):
        self.db = database
        self.setup_social_tables()
    
    def setup_social_tables(self):
        """Setup social feature tables"""
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS friendships (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    player1_id INTEGER,
                    player2_id INTEGER,
                    status TEXT DEFAULT 'pending',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (player1_id) REFERENCES users (id),
                    FOREIGN KEY (player2_id) REFERENCES users (id)
                )
            ''')
            
            conn.execute('''
                CREATE TABLE IF NOT EXISTS challenges (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    challenger_id INTEGER,
                    challenged_id INTEGER,
                    word_id INTEGER,
                    challenger_score INTEGER,
                    challenged_score INTEGER,
                    status TEXT DEFAULT 'pending',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    completed_at TIMESTAMP,
                    FOREIGN KEY (challenger_id) REFERENCES users (id),
                    FOREIGN KEY (challenged_id) REFERENCES users (id),
                    FOREIGN KEY (word_id) REFERENCES words (id)
                )
            ''')
    
    def send_friend_request(self, sender_id: int, recipient_username: str) -> bool:
        """Send friend request"""
        with sqlite3.connect(self.db.db_path) as conn:
            # Get recipient ID
            cursor = conn.execute(
                "SELECT id FROM users WHERE username = ?",
                (recipient_username,)
            )
            recipient = cursor.fetchone()
            
            if not recipient:
                return False
            
            recipient_id = recipient[0]
            
            # Check if friendship already exists
            cursor = conn.execute("""
                SELECT id FROM friendships 
                WHERE (player1_id = ? AND player2_id = ?) 
                   OR (player1_id = ? AND player2_id = ?)
            """, (sender_id, recipient_id, recipient_id, sender_id))
            
            if cursor.fetchone():
                return False  # Friendship already exists
            
            # Create friend request
            conn.execute(
                "INSERT INTO friendships (player1_id, player2_id) VALUES (?, ?)",
                (sender_id, recipient_id)
            )
            
            return True
    
    def accept_friend_request(self, friendship_id: int) -> bool:
        """Accept friend request"""
        with sqlite3.connect(self.db.db_path) as conn:
            conn.execute(
                "UPDATE friendships SET status = 'accepted' WHERE id = ?",
                (friendship_id,)
            )
            return True
    
    def create_challenge(self, challenger_id: int, challenged_username: str, word_id: int) -> int:
        """Create a challenge between players"""
        with sqlite3.connect(self.db.db_path) as conn:
            # Get challenged player ID
            cursor = conn.execute(
                "SELECT id FROM users WHERE username = ?",
                (challenged_username,)
            )
            challenged = cursor.fetchone()
            
            if not challenged:
                return None
            
            challenged_id = challenged[0]
            
            # Create challenge
            cursor = conn.execute(
                "INSERT INTO challenges (challenger_id, challenged_id, word_id) VALUES (?, ?, ?)",
                (challenger_id, challenged_id, word_id)
            )
            
            return cursor.lastrowid
    
    def get_global_leaderboard(self, period: str = 'all_time', limit: int = 10) -> List[Dict]:
        """Get global leaderboard"""
        with sqlite3.connect(self.db.db_path) as conn:
            base_query = """
                SELECT u.username, u.achievement_points,
                       COUNT(gh.id) as total_games,
                       COUNT(CASE WHEN gh.won THEN 1 END) as games_won,
                       AVG(CASE WHEN gh.won THEN gh.duration END) as avg_win_time
                FROM users u
                LEFT JOIN game_history gh ON u.id = gh.player_id
            """
            
            if period == 'weekly':
                base_query += " WHERE gh.created_at >= date('now', '-7 days')"
            elif period == 'monthly':
                base_query += " WHERE gh.created_at >= date('now', '-30 days')"
            
            base_query += """
                GROUP BY u.id, u.username, u.achievement_points
                HAVING total_games > 0
                ORDER BY games_won DESC, avg_win_time ASC
                LIMIT ?
            """
            
            cursor = conn.execute(base_query, (limit,))
            
            leaderboard = []
            for i, row in enumerate(cursor.fetchall(), 1):
                leaderboard.append({
                    'rank': i,
                    'username': row[0],
                    'achievement_points': row[1],
                    'total_games': row[2],
                    'games_won': row[3],
                    'win_rate': (row[3] / row[2] * 100) if row[2] > 0 else 0,
                    'avg_win_time': row[4] or 0
                })
            
            return leaderboard

Advanced GUI Enhancements

1. Modern Theme System

hangmandatabase.py
def apply_theme(self, theme_name: str = 'default'):
    """Apply visual theme to GUI"""
    themes = {
        'default': {
            'bg_color': '#f0f0f0',
            'text_color': '#333333',
            'accent_color': '#4CAF50',
            'button_color': '#2196F3',
            'error_color': '#f44336'
        },
        'dark': {
            'bg_color': '#2b2b2b',
            'text_color': '#ffffff',
            'accent_color': '#81C784',
            'button_color': '#64B5F6',
            'error_color': '#e57373'
        },
        'ocean': {
            'bg_color': '#e1f5fe',
            'text_color': '#01579b',
            'accent_color': '#00bcd4',
            'button_color': '#0288d1',
            'error_color': '#f44336'
        }
    }
    
    if theme_name not in themes:
        theme_name = 'default'
    
    theme = themes[theme_name]
    
    # Apply theme to root window
    self.root.configure(bg=theme['bg_color'])
    
    # Apply to all frames and widgets
    self.apply_theme_to_widgets(self.root, theme)
 
def apply_theme_to_widgets(self, parent, theme):
    """Recursively apply theme to all widgets"""
    for child in parent.winfo_children():
        widget_class = child.winfo_class()
        
        if widget_class in ['Frame', 'Toplevel']:
            child.configure(bg=theme['bg_color'])
        elif widget_class == 'Label':
            child.configure(bg=theme['bg_color'], fg=theme['text_color'])
        elif widget_class == 'Button':
            child.configure(bg=theme['button_color'], fg='white')
        elif widget_class in ['Entry', 'Text']:
            child.configure(bg='white', fg=theme['text_color'])
        
        # Recursively apply to children
        self.apply_theme_to_widgets(child, theme)
hangmandatabase.py
def apply_theme(self, theme_name: str = 'default'):
    """Apply visual theme to GUI"""
    themes = {
        'default': {
            'bg_color': '#f0f0f0',
            'text_color': '#333333',
            'accent_color': '#4CAF50',
            'button_color': '#2196F3',
            'error_color': '#f44336'
        },
        'dark': {
            'bg_color': '#2b2b2b',
            'text_color': '#ffffff',
            'accent_color': '#81C784',
            'button_color': '#64B5F6',
            'error_color': '#e57373'
        },
        'ocean': {
            'bg_color': '#e1f5fe',
            'text_color': '#01579b',
            'accent_color': '#00bcd4',
            'button_color': '#0288d1',
            'error_color': '#f44336'
        }
    }
    
    if theme_name not in themes:
        theme_name = 'default'
    
    theme = themes[theme_name]
    
    # Apply theme to root window
    self.root.configure(bg=theme['bg_color'])
    
    # Apply to all frames and widgets
    self.apply_theme_to_widgets(self.root, theme)
 
def apply_theme_to_widgets(self, parent, theme):
    """Recursively apply theme to all widgets"""
    for child in parent.winfo_children():
        widget_class = child.winfo_class()
        
        if widget_class in ['Frame', 'Toplevel']:
            child.configure(bg=theme['bg_color'])
        elif widget_class == 'Label':
            child.configure(bg=theme['bg_color'], fg=theme['text_color'])
        elif widget_class == 'Button':
            child.configure(bg=theme['button_color'], fg='white')
        elif widget_class in ['Entry', 'Text']:
            child.configure(bg='white', fg=theme['text_color'])
        
        # Recursively apply to children
        self.apply_theme_to_widgets(child, theme)

2. Animation System

hangmandatabase.py
def animate_hangman_drawing(self, stage: int):
    """Animate hangman drawing with smooth transitions"""
    if not hasattr(self, 'canvas'):
        return
    
    # Clear previous drawing
    self.canvas.delete("hangman")
    
    # Animate drawing each part
    parts = [
        self.draw_gallows,
        self.draw_head,
        self.draw_body,
        self.draw_left_arm,
        self.draw_right_arm,
        self.draw_left_leg,
        self.draw_right_leg
    ]
    
    for i in range(min(stage + 1, len(parts))):
        self.root.after(i * 200, lambda idx=i: parts[idx]())
 
def draw_gallows(self):
    """Draw gallows structure"""
    # Base
    self.canvas.create_line(20, 180, 120, 180, width=3, tags="hangman", fill="brown")
    # Pole
    self.canvas.create_line(40, 180, 40, 20, width=3, tags="hangman", fill="brown")
    # Top beam
    self.canvas.create_line(40, 20, 100, 20, width=3, tags="hangman", fill="brown")
    # Noose
    self.canvas.create_line(100, 20, 100, 40, width=2, tags="hangman", fill="black")
 
def draw_head(self):
    """Draw head with animation"""
    self.canvas.create_oval(85, 40, 115, 70, outline="black", width=2, tags="hangman")
    # Add face after a delay
    self.root.after(100, self.draw_face)
 
def draw_face(self):
    """Draw facial features"""
    # Eyes
    self.canvas.create_oval(92, 48, 96, 52, fill="black", tags="hangman")
    self.canvas.create_oval(104, 48, 108, 52, fill="black", tags="hangman")
    # Mouth (sad face)
    self.canvas.create_arc(95, 58, 105, 65, start=0, extent=180, outline="black", tags="hangman")
hangmandatabase.py
def animate_hangman_drawing(self, stage: int):
    """Animate hangman drawing with smooth transitions"""
    if not hasattr(self, 'canvas'):
        return
    
    # Clear previous drawing
    self.canvas.delete("hangman")
    
    # Animate drawing each part
    parts = [
        self.draw_gallows,
        self.draw_head,
        self.draw_body,
        self.draw_left_arm,
        self.draw_right_arm,
        self.draw_left_leg,
        self.draw_right_leg
    ]
    
    for i in range(min(stage + 1, len(parts))):
        self.root.after(i * 200, lambda idx=i: parts[idx]())
 
def draw_gallows(self):
    """Draw gallows structure"""
    # Base
    self.canvas.create_line(20, 180, 120, 180, width=3, tags="hangman", fill="brown")
    # Pole
    self.canvas.create_line(40, 180, 40, 20, width=3, tags="hangman", fill="brown")
    # Top beam
    self.canvas.create_line(40, 20, 100, 20, width=3, tags="hangman", fill="brown")
    # Noose
    self.canvas.create_line(100, 20, 100, 40, width=2, tags="hangman", fill="black")
 
def draw_head(self):
    """Draw head with animation"""
    self.canvas.create_oval(85, 40, 115, 70, outline="black", width=2, tags="hangman")
    # Add face after a delay
    self.root.after(100, self.draw_face)
 
def draw_face(self):
    """Draw facial features"""
    # Eyes
    self.canvas.create_oval(92, 48, 96, 52, fill="black", tags="hangman")
    self.canvas.create_oval(104, 48, 108, 52, fill="black", tags="hangman")
    # Mouth (sad face)
    self.canvas.create_arc(95, 58, 105, 65, start=0, extent=180, outline="black", tags="hangman")

3. Sound Effects Integration

hangmandatabase.py
import pygame
import os
 
class SoundManager:
    def __init__(self):
        try:
            pygame.mixer.init()
            self.sounds_enabled = True
            self.load_sounds()
        except:
            self.sounds_enabled = False
    
    def load_sounds(self):
        """Load game sound effects"""
        sound_dir = "sounds"
        self.sounds = {}
        
        sound_files = {
            'correct_letter': 'correct.wav',
            'wrong_letter': 'wrong.wav',
            'game_won': 'victory.wav',
            'game_lost': 'defeat.wav',
            'button_click': 'click.wav',
            'achievement': 'achievement.wav'
        }
        
        for sound_name, filename in sound_files.items():
            try:
                sound_path = os.path.join(sound_dir, filename)
                if os.path.exists(sound_path):
                    self.sounds[sound_name] = pygame.mixer.Sound(sound_path)
            except:
                pass
    
    def play_sound(self, sound_name: str):
        """Play sound effect"""
        if self.sounds_enabled and sound_name in self.sounds:
            try:
                self.sounds[sound_name].play()
            except:
                pass
    
    def set_volume(self, volume: float):
        """Set master volume (0.0 to 1.0)"""
        if self.sounds_enabled:
            for sound in self.sounds.values():
                sound.set_volume(volume)
hangmandatabase.py
import pygame
import os
 
class SoundManager:
    def __init__(self):
        try:
            pygame.mixer.init()
            self.sounds_enabled = True
            self.load_sounds()
        except:
            self.sounds_enabled = False
    
    def load_sounds(self):
        """Load game sound effects"""
        sound_dir = "sounds"
        self.sounds = {}
        
        sound_files = {
            'correct_letter': 'correct.wav',
            'wrong_letter': 'wrong.wav',
            'game_won': 'victory.wav',
            'game_lost': 'defeat.wav',
            'button_click': 'click.wav',
            'achievement': 'achievement.wav'
        }
        
        for sound_name, filename in sound_files.items():
            try:
                sound_path = os.path.join(sound_dir, filename)
                if os.path.exists(sound_path):
                    self.sounds[sound_name] = pygame.mixer.Sound(sound_path)
            except:
                pass
    
    def play_sound(self, sound_name: str):
        """Play sound effect"""
        if self.sounds_enabled and sound_name in self.sounds:
            try:
                self.sounds[sound_name].play()
            except:
                pass
    
    def set_volume(self, volume: float):
        """Set master volume (0.0 to 1.0)"""
        if self.sounds_enabled:
            for sound in self.sounds.values():
                sound.set_volume(volume)

Performance Optimization and Caching

1. Database Query Optimization

hangmandatabase.py
def optimize_database_performance(self):
    """Optimize database for better performance"""
    with sqlite3.connect(self.db_path) as conn:
        # Create comprehensive indexes
        indexes = [
            "CREATE INDEX IF NOT EXISTS idx_game_history_player_id ON game_history(player_id)",
            "CREATE INDEX IF NOT EXISTS idx_game_history_created_at ON game_history(created_at)",
            "CREATE INDEX IF NOT EXISTS idx_game_history_won ON game_history(won)",
            "CREATE INDEX IF NOT EXISTS idx_words_category ON words(category)",
            "CREATE INDEX IF NOT EXISTS idx_words_difficulty ON words(difficulty)",
            "CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)",
            "CREATE INDEX IF NOT EXISTS idx_player_achievements_player_id ON player_achievements(player_id)"
        ]
        
        for index_sql in indexes:
            conn.execute(index_sql)
        
        # Analyze tables for query optimization
        conn.execute("ANALYZE")
        
        # Set pragmas for better performance
        conn.execute("PRAGMA cache_size = 10000")
        conn.execute("PRAGMA temp_store = MEMORY")
        conn.execute("PRAGMA journal_mode = WAL")
 
import functools
import time
 
class ResultCache:
    def __init__(self, ttl_seconds: int = 300):
        self.cache = {}
        self.ttl = ttl_seconds
    
    def get(self, key: str):
        """Get cached result"""
        if key in self.cache:
            result, timestamp = self.cache[key]
            if time.time() - timestamp < self.ttl:
                return result
            else:
                del self.cache[key]
        return None
    
    def set(self, key: str, value):
        """Set cached result"""
        self.cache[key] = (value, time.time())
    
    def clear(self):
        """Clear cache"""
        self.cache.clear()
 
# Global cache instance
result_cache = ResultCache()
 
def cached_db_query(cache_key_func):
    """Decorator for caching database queries"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            cache_key = cache_key_func(*args, **kwargs)
            cached_result = result_cache.get(cache_key)
            
            if cached_result is not None:
                return cached_result
            
            result = func(*args, **kwargs)
            result_cache.set(cache_key, result)
            return result
        
        return wrapper
    return decorator
 
@cached_db_query(lambda self, player_id: f"player_stats_{player_id}")
def get_player_stats_cached(self, player_id: int) -> Dict:
    """Cached version of get_player_stats"""
    return self.get_player_stats(player_id)
hangmandatabase.py
def optimize_database_performance(self):
    """Optimize database for better performance"""
    with sqlite3.connect(self.db_path) as conn:
        # Create comprehensive indexes
        indexes = [
            "CREATE INDEX IF NOT EXISTS idx_game_history_player_id ON game_history(player_id)",
            "CREATE INDEX IF NOT EXISTS idx_game_history_created_at ON game_history(created_at)",
            "CREATE INDEX IF NOT EXISTS idx_game_history_won ON game_history(won)",
            "CREATE INDEX IF NOT EXISTS idx_words_category ON words(category)",
            "CREATE INDEX IF NOT EXISTS idx_words_difficulty ON words(difficulty)",
            "CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)",
            "CREATE INDEX IF NOT EXISTS idx_player_achievements_player_id ON player_achievements(player_id)"
        ]
        
        for index_sql in indexes:
            conn.execute(index_sql)
        
        # Analyze tables for query optimization
        conn.execute("ANALYZE")
        
        # Set pragmas for better performance
        conn.execute("PRAGMA cache_size = 10000")
        conn.execute("PRAGMA temp_store = MEMORY")
        conn.execute("PRAGMA journal_mode = WAL")
 
import functools
import time
 
class ResultCache:
    def __init__(self, ttl_seconds: int = 300):
        self.cache = {}
        self.ttl = ttl_seconds
    
    def get(self, key: str):
        """Get cached result"""
        if key in self.cache:
            result, timestamp = self.cache[key]
            if time.time() - timestamp < self.ttl:
                return result
            else:
                del self.cache[key]
        return None
    
    def set(self, key: str, value):
        """Set cached result"""
        self.cache[key] = (value, time.time())
    
    def clear(self):
        """Clear cache"""
        self.cache.clear()
 
# Global cache instance
result_cache = ResultCache()
 
def cached_db_query(cache_key_func):
    """Decorator for caching database queries"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            cache_key = cache_key_func(*args, **kwargs)
            cached_result = result_cache.get(cache_key)
            
            if cached_result is not None:
                return cached_result
            
            result = func(*args, **kwargs)
            result_cache.set(cache_key, result)
            return result
        
        return wrapper
    return decorator
 
@cached_db_query(lambda self, player_id: f"player_stats_{player_id}")
def get_player_stats_cached(self, player_id: int) -> Dict:
    """Cached version of get_player_stats"""
    return self.get_player_stats(player_id)

This comprehensive hangman game demonstrates advanced database integration, user management, game mechanics, and modern GUI development. The system provides enterprise-level features while maintaining an engaging gaming experience. 🎮🎯

Was this page helpful?

Let us know how we did