semantrix / game.py
Javierss
Add word check and change ln to log10
f60623c
raw
history blame
13.6 kB
# """
# 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:
# 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
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"]
# Create empty KeyedVectors model with predefined size where the embeddings will be stored if Sentence Transformer used
if self.model_type == "SentenceTransformer":
self.model_trans = KeyedVectors(768)
self.model_st = SentenceTransformer(
"sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
)
# 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.model_type == "SentenceTransformer":
# 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.secret not in self.model_trans.key_to_index.keys():
self.model_trans.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():
# If the word is not in the model, 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
# Add the word to the KeyedVectors model if the model type is SentenceTransformer and the word is not already in the model
if (
self.model_type == "SentenceTransformer"
and word not in self.model_trans.key_to_index.keys()
):
self.model_trans.add_vector(
word, self.model_st.encode(word, convert_to_tensor=True).tolist()
)
# 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
if self.model_type == "word2vec":
similarity = self.model.similarity(self.secret, word)
else:
similarity = self.model_trans.similarity(self.secret, word)
log_similarity = np.log10((similarity if similarity > 0 else 0) * 10)
score = round(
np.interp(
log_similarity,
[0, np.log10(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