File size: 13,632 Bytes
f60623c 7c2eafe f60623c 7c2eafe f60623c 7c2eafe f60623c 7c2eafe f60623c 7c2eafe f60623c 7c2eafe f60623c 7c2eafe f60623c 7c2eafe f60623c 7c2eafe |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# """
# 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
|