Spaces:
Sleeping
Sleeping
""" | |
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 | |