""" Semantrix Game Module This module defines the Semantrix class, which implements a word guessing game using word embeddings. The game can be configured to use either a Word2Vec model or a SentenceTransformer model for word embeddings. The game supports multiple languages and difficulty levels. Classes: Semantrix: A class that implements the Semantrix word guessing game. Semantrix.DictWrapper: A helper class to wrap configuration dictionaries. Functions: __init__(self, lang=0, model_type="SentenceTransformer"): Initializes the Semantrix game with the specified language and model type. prepare_game(self, difficulty): Prepares the game with the selected difficulty level. gen_rank(self, repeated): Generates the ranking file based on the scores. play_game(self, word): Plays the game with the selected word and returns feedback. curiosity(self): Generates a curiosity hint about the secret word once the game is over. Attributes: model (KeyedVectors): The word embeddings model. config_file_path (str): Path to the configuration file. secret_file_path (str): Path to the secret words file. data_path (str): Path to the data directory. Config_full (dict): Full configuration data. secret (dict): Secret words data. lang (int): Language of the game (0 for Spanish, 1 for English). model_type (str): Type of the model ("word2vec" or "SentenceTransformer"). Config (DictWrapper): Configuration data for the selected language. secret_dict (dict): Secret words for the selected language. secret_list (list): List of secret words for the selected difficulty. words (list): List of words guessed by the player. scores (list): List of scores for the guessed words. win (bool): Indicates if the player has won the game. n (int): Number of hints given. recent_hint (int): Counter for recent hints. f_dev_avg (float): Moving average of the tendency slope. last_hint (int): Index of the last hint given. difficulty (int): Difficulty level of the game. """ import json import random from datetime import datetime import numpy as np from gensim.models import KeyedVectors from hints import curiosity, hint from tracking import ( calculate_moving_average, calculate_tendency_slope, ) from sentence_transformers import SentenceTransformer import warnings warnings.filterwarnings(action="ignore", category=UserWarning, module="gensim") # Define the class Semantrix class Semantrix: # Create empty KeyedVectors model with predefined size where the embeddings will be stored model = KeyedVectors(768) # Define the paths for the configuration files and the data config_file_path = "config/lang.json" secret_file_path = "config/secret.json" data_path = "data/" # Define the class DictWrapper to store the configuration data class DictWrapper: def __init__(self, data_dict): self.__dict__.update(data_dict) # Define the constructor of the class which loads the configuration files and initializes the class variables depending on the language parameter and the model type def __init__(self, lang=0, model_type="SentenceTransformer"): # Load the configuration files with open(self.config_file_path, "r") as file: self.Config_full = json.load(file) # Load the secret file where the secret words are stored with open(self.secret_file_path, "r") as file: self.secret = json.load(file) # Set the language of the game self.lang = lang # Set the model type self.model_type = model_type # Load word2vec model if needed if self.model_type == "word2vec": if self.lang == 1: self.model = KeyedVectors.load( "config/w2v_models/eng_w2v_model", mmap="r" ) self.Config = self.DictWrapper(self.Config_full["ENG"]["Game"]) self.secret_dict = self.secret["ENG"] else: self.model = KeyedVectors.load( "config/w2v_models/esp_w2v_model", mmap="r" ) self.Config = self.DictWrapper(self.Config_full["SPA"]["Game"]) self.secret_dict = self.secret["SPA"] else: self.model_st = SentenceTransformer( "sentence-transformers/paraphrase-multilingual-mpnet-base-v2" ) # Set the configuration variables depending on the language if self.lang == 1: self.Config = self.DictWrapper(self.Config_full["ENG"]["Game"]) self.secret_dict = self.secret["ENG"] else: self.Config = self.DictWrapper(self.Config_full["SPA"]["Game"]) self.secret_dict = self.secret["SPA"] # Create the ranking file with open(self.data_path + "ranking.txt", "w+") as file: file.write("---------------------------") # Define the function to prepare the game with the selected difficulty def prepare_game(self, difficulty): # Set the secret list depending on the difficulty self.secret_list = ( self.secret_dict["basic"] if difficulty <= 2 else self.secret_dict["advanced"] ) # Select a random secret word from the secret list self.secret = self.secret_list.pop(random.randint(0, len(self.secret_list) - 1)) self.secret = self.secret.lower() # Store the secret word in the words list self.words = [self.Config.secret_word] # Store the score in the scores list self.scores = [10] # Store the embedding of the secret word in the embeddings dictionary if self.secret not in self.model.key_to_index.keys(): # Add the secret word to the KeyedVectors model if the model type is SentenceTransformer # If the model type is word2vec, the secret word is already in the model if self.model_type == "SentenceTransformer": self.model.add_vector( self.secret, self.model_st.encode(self.secret, convert_to_tensor=True).tolist(), ) # Initialize the game variables self.win = False self.n = 0 self.recent_hint = 0 self.f_dev_avg = 0 self.last_hint = -1 self.difficulty = difficulty # Set the number of hints depending on the difficulty if self.difficulty == 1: self.n = 3 # Define the function to generate the ranking file def gen_rank(self, repeated): ascending_indices = np.argsort(self.scores) descending_indices = list(ascending_indices[::-1]) ranking_data = [] k = len(self.words) - 1 if repeated != -1: k = repeated ranking_data.append(["#" + str(k), self.words[k], self.scores[k]]) ranking_data.append("---------------------------") for i in descending_indices: if i == 0: continue ranking_data.append(["#" + str(i), self.words[i], self.scores[i]]) with open(self.data_path + "ranking.txt", "w+") as file: for item in ranking_data: file.write("%s\n" % item) # Define the function to play the game with the selected word def play_game(self, word): # Convert the word to lowercase word = word.lower() # Check if the user wants to give up if word == "give_up": text = ( "[lose]" + self.Config.Feedback_9 + self.secret + "\n\n" + self.Config.Feedback_10 ) return text # Check if the word is repeated if word in self.words: repeated = self.words.index(word) else: repeated = -1 self.words.append(word) # Check if the word is in the model already if word not in self.model.key_to_index.keys(): # Add the word to the KeyedVectors model if the model type is SentenceTransformer if self.model_type == "SentenceTransformer": self.model.add_vector( word, self.model_st.encode(word, convert_to_tensor=True).tolist() ) else: # If the word is not in the model when using word2vec, remove it from the words list and provide feedback self.words.pop(len(self.words) - 1) feedback = ( "I don't know that word. Try again." if self.lang == 1 else "No conozco esa palabra. Inténtalo de nuevo." ) feedback += ( "[rank]" + open(self.data_path + "ranking.txt", "r").read() if len(self.words) > 1 else "\n\n" ) return feedback # Calculate the score of the word, apply logarithmic scaling, interpolate the score to a range from 0 to 10, and round it to two decimal places score = round( np.interp( np.log(self.model.similarity(self.secret, word) * 10), [0, np.log(10)], [0, 10], ), 2, ) # Remove the word from the score list if it is repeated if repeated == -1: self.scores.append(score) # Generate the feedback message depending on the score if score <= 2.5: feedback = self.Config.Feedback_0 + str(score) elif score > 2.5 and score <= 4.0: feedback = self.Config.Feedback_1 + str(score) elif score > 4.0 and score <= 6.0: feedback = self.Config.Feedback_2 + str(score) elif score > 6.0 and score <= 7.5: feedback = self.Config.Feedback_3 + str(score) elif score > 7.5 and score <= 8.0: feedback = self.Config.Feedback_4 + str(score) elif score > 8.0 and score < 10.0: feedback = self.Config.Feedback_5 + str(score) # If the score is 10, the user wins the game else: self.win = True feedback = "[win]" + self.Config.Feedback_8 self.words[0] = self.secret self.words.pop(len(self.words) - 1) self.scores.pop(len(self.scores) - 1) # Generate the feedback message depending on the score and the previous score if score > self.scores[len(self.scores) - 2] and self.win == False: feedback += "\n" + self.Config.Feedback_6 elif score < self.scores[len(self.scores) - 2] and self.win == False: feedback += "\n" + self.Config.Feedback_7 ## Hint generation # If the difficulty is not 4, calculate the moving average of the scores and the tendency slope if self.difficulty != 4: mov_avg = calculate_moving_average(self.scores[1:], 5) # If the moving average has more than one element and the user has not won yet, calculate the tendency slope and the moving average of the tendency slope if len(mov_avg) > 1 and self.win == False: f_dev = calculate_tendency_slope(mov_avg) f_dev_avg = calculate_moving_average(f_dev, 3) # If the tendency slope is negative and the hint has not been given recently (at least three rounds earlier), generate a hint if f_dev_avg[len(f_dev_avg) - 1] < 0 and self.recent_hint == 0: # Generate a random hint intro from the hint list i = random.randint(0, len(self.Config.hint_intro) - 1) feedback += "\n\n[hint]" + self.Config.hint_intro[i] # Generate a dynamic hint hint_text, self.n, self.last_hint = hint( self.secret, self.n, self.model_st, self.last_hint, self.lang, ( self.DictWrapper(self.Config_full["ENG"]["Hint"]) if self.lang == 1 else self.DictWrapper(self.Config_full["SPA"]["Hint"]) ), ) feedback += "\n" + hint_text self.recent_hint = 3 if self.recent_hint != 0: self.recent_hint -= 1 # Generate the ranking file self.gen_rank(repeated) # Add the ranking file to the feedback message feedback += "[rank]" + open(self.data_path + "ranking.txt", "r").read() # Save the ranking file with the plays of the user if the user wins if self.win: with open(self.data_path + "ranking.txt", "r") as original_file: file_content = original_file.readlines() new_file_name = self.secret + "_" + str(datetime.now()) + ".txt" with open(self.data_path + "plays/" + new_file_name, "w+") as new_file: new_file.writelines(file_content[2:]) # Return the feedback message return feedback # Define the function to generate a curiosity hint once the game is over def curiosity(self): # Generate a curiosity aboyt the secret word feedback = curiosity( self.secret, ( self.DictWrapper(self.Config_full["ENG"]["Hint"]) if self.lang == 1 else self.DictWrapper(self.Config_full["SPA"]["Hint"]) ), ) # Return the feedback message return feedback