import json
import requests
import random
import string
import secrets
import time
import re
import collections
from urllib.parse import parse_qs, urlencode, urlparse
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# Vowels list for analysis
vowels = ['a', 'e', 'i', 'o', 'u']

# Function to calculate vowel ratio in a word
def vowel_count(clean_word):
    count = 0
    for i in clean_word:
        if i in vowels:
            count += 1.0
    return count / len(clean_word) if len(clean_word) > 0 else 0

# Load and clean the dictionary
def load_dictionary(file_path):
    try:
        with open(file_path, "r") as f:
            words = [line.strip() for line in f]
        return words
    except FileNotFoundError:
        print(f"Dictionary file not found at {file_path}. Please verify the path.")
        return []

# Analyze vowel ratios
def analyze_vowels(words):
    ratios = [vowel_count(word) for word in words]
    series = pd.Series(ratios)
    print(series.describe())
    # Plot histogram
    bins = np.arange(0.0, 1.05, 0.05)
    plt.figure(figsize=(10,6))
    plt.hist(series, bins=bins, edgecolor='black')
    plt.title('Histogram of Vowel Ratios in Words')
    plt.xlabel('Vowel Ratio')
    plt.ylabel('Frequency')
    plt.show()

# Precompute N-grams
def build_n_word_dictionary(words, max_length):
    n_word_dictionary = {i:[] for i in range(3, max_length+1)}
    for word in words:
        word_length = len(word)
        for count in range(3, min(word_length+1, max_length+1)):
            for i in range(word_length - count + 1):
                n_word_dictionary[count].append(word[i:i+count])
    return n_word_dictionary

# Function to count letter appearances
def count_letters(word_list):
    dictx = collections.Counter()
    for word in word_list:
        temp = collections.Counter(word)
        for letter in temp:
            dictx[letter] += 1
    return dictx

class HangmanAPI:
    def __init__(self, access_token=None, session=None, timeout=10):
        self.hangman_url = self.determine_hangman_url()
        self.access_token = access_token
        self.session = session or requests.Session()
        self.timeout = timeout
        self.guessed_letters = []
        
        # Load dictionary
        full_dictionary_location = "/kaggle/input/hangmen/words_250000_train.txt"
        self.full_dictionary = self.build_dictionary(full_dictionary_location)
        if not self.full_dictionary:
            raise ValueError("Failed to load the dictionary. Terminating the program.")
        
        self.full_dictionary_common_letter_sorted = collections.Counter("".join(self.full_dictionary)).most_common()
        
        self.current_dictionary = []
        
        # RL variables
        self.q_table = {}
        self.learning_rate = 0.1
        self.discount_factor = 0.9
        self.epsilon = 1.0
        self.epsilon_decay = 0.995
        self.min_epsilon = 0.01
        
        # Statistics
        self.total_practice_runs = 0
        self.total_recorded_runs = 0
        self.total_recorded_successes = 0
        self.total_practice_successes = 0

    @staticmethod
    def determine_hangman_url():
        links = ['https://trexsim.com', 'https://sg.trexsim.com']
        data = {link: 0 for link in links}
        
        for link in links:
            try:
                s = time.time()
                response = requests.get(link, timeout=5)
                if response.status_code == 200:
                    elapsed = time.time() - s
                    data[link] = elapsed
                else:
                    data[link] = float('inf')
            except requests.RequestException:
                data[link] = float('inf')
                
        sorted_links = sorted(data.items(), key=lambda x: x[1])
        best_link = sorted_links[0][0] if sorted_links else 'https://trexsim.com'
        hangman_endpoint = '/trexsim/hangman'
        return best_link + hangman_endpoint
    
    def build_dictionary(self, file_path):
        return load_dictionary(file_path)
    
    def request(self, endpoint, data=None, method="POST"):
        url = self.hangman_url + endpoint
        headers = {'Authorization': f'Bearer {self.access_token}'} if self.access_token else {}
        try:
            if method.upper() == "POST":
                response = self.session.post(url, json=data, headers=headers, timeout=self.timeout)
            elif method.upper() == "GET":
                response = self.session.get(url, params=data, headers=headers, timeout=self.timeout)
            else:
                print(f"Unsupported HTTP method: {method}")
                return {}
            
            response.raise_for_status()
            return response.json()
        except requests.HTTPError as http_err:
            print(f"HTTP error occurred: {http_err} - URL: {url} - Method: {method}")
        except requests.RequestException as req_err:
            print(f"Request error occurred: {req_err} - URL: {url} - Method: {method}")
        except json.JSONDecodeError:
            print(f"Failed to decode JSON response from {url}")
        return {}
    
    def build_state(self, word, guessed_letters, tries_left):
        """Create a hashable state from the current word, guessed letters, and tries left."""
        clean_word = word.replace(" ", "")  # Remove spaces
        state = (clean_word, "".join(sorted(guessed_letters)), tries_left)
        return state
    
    def update_q_table(self, state, action, reward, next_state):
        """Update Q-Table using Q-Learning formula."""
        if state not in self.q_table:
            self.q_table[state] = {letter: 0 for letter in string.ascii_lowercase}
        
        if next_state not in self.q_table:
            self.q_table[next_state] = {letter: 0 for letter in string.ascii_lowercase}
        
        q_current = self.q_table[state][action]
        q_next_max = max(self.q_table[next_state].values()) if self.q_table[next_state] else 0
        
        # Q-Learning update rule
        self.q_table[state][action] = q_current + self.learning_rate * (reward + self.discount_factor * q_next_max - q_current)
    
    def guess(self, word, tries_left):
        """Guess the next letter based on the RL algorithm."""
        state = self.build_state(word, self.guessed_letters, tries_left)
        
        # Epsilon-greedy action selection
        if random.uniform(0, 1) < self.epsilon:
            # Explore: Random action
            available_actions = [letter for letter in string.ascii_lowercase if letter not in self.guessed_letters]
            if not available_actions:
                return None
            guess_letter = random.choice(available_actions)
        else:
            # Exploit: Choose best action based on Q-Table
            if state not in self.q_table:
                self.q_table[state] = {letter: 0 for letter in string.ascii_lowercase}
            
            sorted_actions = sorted(self.q_table[state].items(), key=lambda x: x[1], reverse=True)
            guess_letter = None
            for letter, _ in sorted_actions:
                if letter not in self.guessed_letters:
                    guess_letter = letter
                    break
            if not guess_letter:
                available_actions = [letter for letter in string.ascii_lowercase if letter not in self.guessed_letters]
                guess_letter = random.choice(available_actions) if available_actions else None
        
        return guess_letter
    
    def reward_function(self, correct_guess):
        """Assign a reward based on the result of the guess."""
        return 10 if correct_guess else -5
    
    def start_game(self, practice=True, verbose=True):
        """Play a full game of hangman, implementing the RL-based guessing."""
        # Reset guessed letters and dictionary
        self.guessed_letters = []
        self.current_dictionary = self.full_dictionary
        
        # Start new game
        response = self.request("/new_game", method="GET")  # Changed to GET
        if response.get('status') == "approved":
            game_id = response.get('game_id')
            word = response.get('word')
            tries_remains = response.get('tries_remains')
            
            if verbose:
                print(f"New game started. Game ID: {game_id}. Tries left: {tries_remains}. Word: {word}")
            
            state = self.build_state(word, self.guessed_letters, tries_remains)
            
            while tries_remains > 0:
                # Guess based on RL algorithm
                guess_letter = self.guess(word, tries_remains)
                if not guess_letter:
                    if verbose:
                        print("No available letters to guess.")
                    break
                self.guessed_letters.append(guess_letter)
                
                if verbose:
                    print(f"Guessing letter: {guess_letter}")
                
                # Make the guess and get response
                res = self.request("/guess_letter", {
                    "request": "guess_letter",
                    "game_id": game_id,
                    "letter": guess_letter
                }, method="POST")  # Guessing letters via POST
                
                if verbose:
                    print(f"Server response: {res}")
                
                status = res.get('status')
                tries_remains = res.get('tries_remains')
                correct_guess = res.get('correct_guess', False)
                
                # Assign reward and update Q-Table
                reward = self.reward_function(correct_guess)
                new_word = res.get('word', word)
                next_state = self.build_state(new_word, self.guessed_letters, tries_remains)
                self.update_q_table(state, guess_letter, reward, next_state)
                
                # Update state
                state = next_state
                
                # Update epsilon (decay for exploration-exploitation trade-off)
                self.epsilon = max(self.min_epsilon, self.epsilon * self.epsilon_decay)
                
                if status == "success":
                    if verbose:
                        print("Game won!")
                    self.total_recorded_runs += 1
                    self.total_recorded_successes += 1
                    break
                elif status == "failed":
                    if verbose:
                        print("Game lost.")
                    self.total_recorded_runs += 1
                    break
                elif status == "ongoing":
                    word = res.get('word', word)
        else:
            if verbose:
                print("Failed to start a new game.")
    
    def my_status(self):
        """Get game statistics."""
        return [
            self.total_practice_runs,
            self.total_recorded_runs,
            self.total_recorded_successes,
            self.total_practice_successes
        ]

# Usage Example
if __name__ == "__main__":
    # Initialize HangmanAPI with your access token
    api = HangmanAPI(access_token="775976eaf5c7653d7f9cc3f826ad3e", timeout=10)
    
    # Start a single game
    api.start_game(practice=True, verbose=True)
    
    # Retrieve and display status
    status = api.my_status()
    total_practice_runs, total_recorded_runs, total_recorded_successes, total_practice_successes = status
    practice_success_rate = (total_practice_successes / total_practice_runs) if total_practice_runs > 0 else 0
    print(f'Run {total_practice_runs} practice games out of an allotted 100,000.')
    print(f'Practice success rate: {practice_success_rate:.2%}')
Failed to start a new game.
Run 0 practice games out of an allotted 100,000.
Practice success rate: 0.00%

why this error is coming in hangmen project