Javierss commited on
Commit
b875699
·
1 Parent(s): 8295cba

Clone old repo

Browse files
Files changed (9) hide show
  1. .gitattributes +11 -0
  2. .gitignore +6 -0
  3. README.md +106 -4
  4. app.py +641 -0
  5. game.py +417 -0
  6. gen_model.py +13 -0
  7. hints.py +286 -0
  8. requirements.txt +8 -0
  9. tracking.py +23 -0
.gitattributes CHANGED
@@ -33,3 +33,14 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ # *.gif filter=lfs diff=lfs merge=lfs -text
37
+ config/w2v_models/esp_w2v_model filter=lfs diff=lfs merge=lfs -text
38
+ config/w2v_models/eng_w2v_model filter=lfs diff=lfs merge=lfs -text
39
+ config/words.txt filter=lfs diff=lfs merge=lfs -text
40
+ config/words.gz filter=lfs diff=lfs merge=lfs -text
41
+ config/w2v_models/words.gz filter=lfs diff=lfs merge=lfs -text
42
+ config/possible_words.json filter=lfs diff=lfs merge=lfs -text
43
+ config/strans_models/. filter=lfs diff=lfs merge=lfs -text
44
+ config/strans_models/esp_strans_model filter=lfs diff=lfs merge=lfs -text
45
+ config/strans_models/esp_strans_model.vectors.npy filter=lfs diff=lfs merge=lfs -text
46
+ config/images/logo_win.gif filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ data/plays/*.txt
2
+ data/plays/*.json
3
+ data/plays/data/*.json
4
+ __pycache__/*
5
+ data/rankings/*.txt
6
+ config/*_models
README.md CHANGED
@@ -1,12 +1,114 @@
1
  ---
2
- title: Semantrix Cond2
3
- emoji: 🏆
4
- colorFrom: purple
5
  colorTo: pink
6
  sdk: gradio
7
  sdk_version: 5.22.0
 
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Semantrix
3
+ emoji: 🔠
4
+ colorFrom: indigo
5
  colorTo: pink
6
  sdk: gradio
7
  sdk_version: 5.22.0
8
+ python_version: 3.11.5
9
  app_file: app.py
10
  pinned: false
11
+ license: other
12
  ---
13
 
14
+ # Semantrix Game
15
+
16
+ This repository contains the implementation of the Semantrix game, a word guessing game using word embeddings. The game supports multiple languages (Spanish and English) and can be configured to use either a Word2Vec model or a SentenceTransformer model for word embeddings.
17
+
18
+ ## Modules
19
+
20
+
21
+
22
+ ### app.py
23
+
24
+ This module defines a Gradio-based web application for the Semantrix game. The application allows users to play the game in either Spanish or English, using different embedding models for word similarity.
25
+
26
+ #### Functions
27
+
28
+ - `convert_to_markdown_centered(text)`: Converts text to a centered markdown format for displaying game history and last attempt.
29
+ - **Parameters**:
30
+ - `text (str)`: The text to be converted.
31
+ - **Returns**: `str`: The centered markdown formatted text.
32
+
33
+ - `reset(difficulty, lang, model)`: Resets the game state based on the selected difficulty, language, and model.
34
+ - **Parameters**:
35
+ - `difficulty`: The selected difficulty level.
36
+ - `lang`: The selected language.
37
+ - `model`: The selected embedding model.
38
+ - **Returns**: `list`: A list of initial output components for the UI.
39
+
40
+ - `change(state, inp)`: Changes the game state by incrementing the state variable.
41
+ - **Parameters**:
42
+ - `state`: The current game state.
43
+ - `inp`: The user input.
44
+ - **Returns**: `list`: A list containing the updated state and input component.
45
+
46
+ - `update(state, radio, inp, hint)`: Updates the game state and UI components based on the current state and user inputs.
47
+ - **Parameters**:
48
+ - `state`: The current game state.
49
+ - `radio`: The radio input component.
50
+ - `inp`: The user input.
51
+ - `hint`: The hint state.
52
+ - **Returns**: `list`: A list of updated output components for the UI.
53
+
54
+ ### game.py
55
+
56
+ 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.
57
+
58
+ #### Classes
59
+
60
+ - `Semantrix`: A class that implements the Semantrix word guessing game.
61
+ - **Methods**:
62
+ - `__init__(self, lang=0, model_type="SentenceTransformer")`: Initializes the Semantrix game with the specified language and model type.
63
+ - `prepare_game(self, difficulty)`: Prepares the game with the selected difficulty level.
64
+ - `gen_rank(self, repeated)`: Generates the ranking file based on the scores.
65
+ - `play_game(self, word)`: Plays the game with the selected word and returns feedback.
66
+ - `curiosity(self)`: Generates a curiosity hint about the secret word once the game is over.
67
+
68
+ ### hints.py
69
+
70
+ This module provides functions to generate dynamic hints and curiosities about a secret word using language models (LLMs).
71
+
72
+ #### Functions
73
+
74
+ - `hint(secret, n, model, last_hint, lang, Config)`: Generates a dynamic hint based on the secret word and the number of hints given.
75
+ - **Parameters**:
76
+ - `secret (str)`: The secret word.
77
+ - `n (int)`: The number of hints already given.
78
+ - `model`: The sentence transformer model used for encoding.
79
+ - `last_hint (int)`: The index of the last hint given.
80
+ - `lang (int)`: The language code (0 for Spanish, 1 for English).
81
+ - `Config`: Configuration object containing hint templates.
82
+ - **Returns**: `tuple`: A tuple containing the generated hint (str), the updated number of hints (int), and the index of the last hint given (int).
83
+
84
+ - `curiosity(secret, Config)`: Generates a curiosity about the secret word.
85
+ - **Parameters**:
86
+ - `secret (str)`: The secret word.
87
+ - `Config`: Configuration object containing the curiosity template.
88
+ - **Returns**: `str`: The generated curiosity.
89
+
90
+ - `ireplace(old, new, text)`: Replaces all occurrences of a substring in a string, case-insensitively.
91
+ - **Parameters**:
92
+ - `old (str)`: The substring to be replaced.
93
+ - `new (str)`: The substring to replace with.
94
+ - `text (str)`: The original string.
95
+ - **Returns**: `str`: The modified string with all occurrences of the old substring replaced by the new substring.
96
+
97
+
98
+ ## How to Run
99
+
100
+ 1. Clone the repository.
101
+ 2. Install the required dependencies.
102
+ 3. Run the `app.py` script to launch the Gradio web application.
103
+
104
+ ## Dependencies
105
+
106
+ - Gradio
107
+ - OpenAI
108
+ - SentenceTransformers
109
+ - Gensim
110
+ - NumPy
111
+
112
+ ## License
113
+
114
+ This project is licensed under the MIT License.
app.py ADDED
@@ -0,0 +1,641 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # """
2
+ # This module defines a Gradio-based web application for the Semantrix game. The application allows users to play the game in either Spanish or English, using different embedding models for word similarity.
3
+
4
+ # Modules:
5
+ # gradio: Used for creating the web interface.
6
+ # json: Used for loading configuration files.
7
+ # game: Contains the Semantrix class for game logic.
8
+
9
+ # File Paths:
10
+ # config_file_path: Path to the configuration file.
11
+ # logo_path: Path to the logo image.
12
+ # logo_win_path: Path to the winning logo image.
13
+
14
+ # Functions:
15
+ # convert_to_markdown_centered(text):
16
+ # Converts text to a centered markdown format for displaying game history and last attempt.
17
+
18
+ # reset(difficulty, lang, model):
19
+ # Resets the game state based on the selected difficulty, language, and model.
20
+
21
+ # change(state, inp):
22
+ # Changes the game state by incrementing the state variable.
23
+
24
+ # update(state, radio, inp, hint):
25
+ # Updates the game state and UI components based on the current state and user inputs.
26
+
27
+ # Gradio Components:
28
+ # demo: The main Gradio Blocks component that contains the entire UI layout.
29
+ # header: A Markdown component for displaying the game header.
30
+ # state: A State component for tracking the current game state.
31
+ # difficulty: A State component for tracking the difficulty level.
32
+ # hint: A State component for tracking if a hint is provided.
33
+ # img: An Image component for displaying the game logo.
34
+ # ranking: A Markdown component for displaying the ranking.
35
+ # out: A Textbox component for displaying game messages.
36
+ # hint_out: A Textbox component for displaying hints.
37
+ # radio: A Radio component for user selections.
38
+ # inp: A Textbox component for user input.
39
+ # but: A Button component for several actions.
40
+ # give_up: A Button component for giving up.
41
+ # reload: A Button component for reloading the game.
42
+ # model: A Dropdown component for selecting the embedding model.
43
+ # lang: A Dropdown component for selecting the language.
44
+
45
+ # Events:
46
+ # inp.submit: Triggers the change function on input submission.
47
+ # but.click: Triggers the change function on button click.
48
+ # give_up.click: Triggers the change function on give up button click.
49
+ # radio.input: Triggers the change function on radio input.
50
+ # reload.click: Triggers the reset function on reload button click.
51
+ # demo.load: Triggers the reset function on demo load.
52
+ # lang[0].select: Triggers the reset function on language selection.
53
+ # model[0].select: Triggers the reset function on model selection.
54
+ # state.change: Triggers the update function on state change.
55
+
56
+ # Main:
57
+ # Launches the Gradio application if the script is run as the main module.
58
+ # """
59
+
60
+ import gradio as gr
61
+ import json
62
+ from datetime import datetime, date
63
+ from game import Semantrix, Model_class
64
+ from huggingface_hub import CommitScheduler
65
+ import os
66
+ import logging
67
+
68
+ # Configure logging
69
+ logging.basicConfig(
70
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
71
+ )
72
+ logger = logging.getLogger(__name__)
73
+
74
+
75
+ # File paths for configuration and images
76
+ base_path = os.path.dirname(os.path.abspath(__file__))
77
+ config_file_path = os.path.join(base_path, "config/lang.json")
78
+ logo_path = os.path.join(base_path, "config/images/logo.png")
79
+ logo_win_path = os.path.join(base_path, "config/images/logo_win.gif")
80
+ condition_config_path = os.path.join(base_path, "config/condition.json")
81
+ data_path = os.path.join(base_path, "data")
82
+ plays_path = os.path.join(data_path, "plays")
83
+
84
+ condition_name = "condition_2"
85
+ # Dynamically determine the condition name based on the folder name
86
+ # folder_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
87
+ # condition = folder_name.split("_")[-1][-1]
88
+ # condition_name = "condition_" + condition
89
+
90
+
91
+ with open(condition_config_path, "r") as file:
92
+ condition_config = json.load(file)
93
+
94
+ model = condition_config[condition_name]["model"]
95
+ hints_enabled = condition_config[condition_name]["hints"]
96
+
97
+ # Loading the configuration file
98
+ with open(config_file_path, "r") as file:
99
+ Config_full = json.load(file)
100
+
101
+ scheduler = CommitScheduler(
102
+ repo_id="Jsevisal/semantrix_data_" + condition_name[-1],
103
+ repo_type="dataset",
104
+ folder_path=plays_path,
105
+ path_in_repo="data",
106
+ every=10,
107
+ )
108
+
109
+ lang = 0 # Language configuration flag (0 for Spanish, 1 for English)
110
+
111
+ # Setting the configuration based on the language flag
112
+ if lang == 1:
113
+ Config = Config_full["ENG"]["Game"]
114
+ Menu = Config_full["ENG"]["Menu"]
115
+ else:
116
+ Config = Config_full["SPA"]["Game"]
117
+ Menu = Config_full["SPA"]["Menu"]
118
+
119
+
120
+ # Function to convert text to centered markdown format
121
+ def convert_to_markdown_centered(text):
122
+ lines = text.strip().split("\n")
123
+
124
+ if not lines:
125
+ return ""
126
+
127
+ last_attempt = lines[0]
128
+ history_attempts = lines[2:12]
129
+ markdown = '<div align="center">\n\n'
130
+
131
+ markdown += "## " + Menu["Best_tries"] + "\n"
132
+ markdown += "<table>\n"
133
+ markdown += " <tr>\n"
134
+ markdown += " <th>" + Menu["N_try"] + "</th>\n"
135
+ markdown += " <th>" + Menu["Word"] + "</th>\n"
136
+ markdown += " <th>" + Menu["Score"] + "</th>\n"
137
+ markdown += " </tr>\n"
138
+
139
+ for line in history_attempts:
140
+ items = eval(line.strip())
141
+ markdown += " <tr>\n"
142
+ markdown += f" <td><strong>{items[0]}</strong></td>\n"
143
+ markdown += f" <td>{items[1]}</td>\n"
144
+ markdown += f" <td>{items[2]}</td>\n"
145
+ markdown += " </tr>\n"
146
+
147
+ markdown += "</table>\n\n"
148
+
149
+ last_items = eval(last_attempt)
150
+ markdown += f"## " + Menu["Last_try"] + "\n"
151
+ markdown += (
152
+ f"**{last_items[0]}:** {last_items[1]} - "
153
+ + Menu["Score"]
154
+ + f": {last_items[2]}\n\n"
155
+ )
156
+ markdown += "---\n\n"
157
+
158
+ markdown += "</div>"
159
+
160
+ return markdown
161
+
162
+
163
+ #
164
+ with gr.Blocks(
165
+ theme="ocean",
166
+ ) as demo:
167
+ # Initializing state variables to manage the internal state of the application
168
+ state = gr.State(-1) # State variable to track the current game state
169
+ difficulty = gr.State(-1) # State variable to track the difficulty level
170
+ hint = gr.State(False) # State variable to track if the hint is provided
171
+
172
+ secret_word_used = gr.BrowserState(
173
+ 0
174
+ ) # State variable to track the number of secret words used
175
+
176
+ # Initializing the game using the Semantrix class with default parameters
177
+ game_instances = {}
178
+ sessions_to_remove = {}
179
+ cooldown_time = 120 # Time in seconds to remove the session from the game_instances
180
+ model_class = Model_class(lang=0, model_type=model)
181
+
182
+ # Creating a Markdown component to display the header
183
+ header = gr.Markdown(
184
+ """
185
+ <p style="text-align:center"> """
186
+ + Menu["Header"]
187
+ + """ </p>
188
+ """
189
+ )
190
+
191
+ # Function to reset the game
192
+ def reset(reload, request: gr.Request):
193
+ global Config, game, Menu, model, lang # Declare global variables to modify them within the function
194
+
195
+ if reload:
196
+ game_instances[request.session_hash] = Semantrix(
197
+ lang=0, model_type=model, session_hash=request.session_hash
198
+ )
199
+ game_instances[request.session_hash].reset_game() # Reset the game state
200
+
201
+ logger.info("New session detected: %s", request.session_hash)
202
+ logger.info("Game instances: %s", game_instances)
203
+
204
+ # Define the initial output components for the UI
205
+ output = [
206
+ -1,
207
+ gr.Textbox(visible=False),
208
+ gr.Textbox(visible=False),
209
+ gr.Image(logo_path, visible=True, interactive=False),
210
+ gr.Button(Menu["Start"], visible=True, variant="secondary"),
211
+ gr.Radio(visible=False),
212
+ gr.Textbox(visible=False),
213
+ gr.Button(visible=False),
214
+ gr.Markdown(
215
+ """
216
+ <p style="text-align:center"> """
217
+ + Menu["Header"]
218
+ + """ </p>
219
+ """
220
+ ),
221
+ gr.Button(visible=False),
222
+ ]
223
+
224
+ # Return the initial output components
225
+ return output
226
+
227
+ def remove_game_instance(timer_tick=False, request: gr.Request | None = None):
228
+ request = None if timer_tick else request
229
+
230
+ if request is not None:
231
+ logger.info("Session on inactivity timer: %s", request.session_hash)
232
+ sessions_to_remove[request.session_hash] = datetime.now()
233
+ if len(sessions_to_remove.items()) > 0:
234
+ for session_hash, timestamp in list(sessions_to_remove.items()):
235
+ if (datetime.now() - timestamp).seconds > cooldown_time:
236
+ del sessions_to_remove[session_hash]
237
+
238
+ session_id = game_instances[session_hash].get_session_id()
239
+ del game_instances[session_hash]
240
+
241
+ # Delete ranking file if it exists
242
+ ranking_file = os.path.join(
243
+ data_path, f"rankings/ranking_{session_hash}.txt"
244
+ )
245
+ if os.path.exists(ranking_file):
246
+ os.remove(ranking_file)
247
+
248
+ # Delete plays files if it exists
249
+ session_files = [
250
+ f for f in os.listdir(plays_path) if f.startswith(session_id)
251
+ ]
252
+ for file_name in session_files:
253
+ with open(os.path.join(plays_path, file_name), "r") as file:
254
+ os.remove(os.path.join(plays_path, file_name))
255
+
256
+ logger.info("Deleted session: %s", session_hash)
257
+
258
+ # Function to change the state of the game
259
+ def change(state, inp):
260
+ # Increment the state by 1
261
+ state = state + 1
262
+
263
+ # Return the updated state and input component
264
+ return [state, inp]
265
+
266
+ # Function to update the game state based on the current state of the game
267
+ def update(state, radio, inp, hint, secret_word_used, request: gr.Request):
268
+ global difficulty, hints_enabled
269
+
270
+ # Define the difficulty state
271
+ dif_state = 3
272
+
273
+ # Initialize the output component list with the current state
274
+
275
+ state_int = state
276
+
277
+ output = [state]
278
+
279
+ if request.session_hash in sessions_to_remove:
280
+ sessions_to_remove.pop(request.session_hash)
281
+ logger.info("Session saved: %s", request.session_hash)
282
+
283
+ # Define UI components for the initial state
284
+ if state_int == -1:
285
+ output.extend(
286
+ [
287
+ gr.Button(Menu["Start"], visible=True),
288
+ gr.Radio(label="", visible=False),
289
+ gr.Textbox(
290
+ Config[list(Config.keys())[state_int]], visible=False, label=""
291
+ ),
292
+ gr.Button(Menu["Give_up"], visible=False),
293
+ gr.Textbox(visible=False),
294
+ gr.Image(interactive=False, visible=True),
295
+ gr.Textbox(visible=False),
296
+ gr.Button(visible=False),
297
+ gr.Markdown(visible=False),
298
+ gr.Button(visible=False),
299
+ secret_word_used,
300
+ ]
301
+ )
302
+
303
+ # Define UI components for the first state, ask the user if they want to know the rules
304
+ elif state_int == 1:
305
+ output.extend(
306
+ [
307
+ gr.Button(visible=False),
308
+ gr.Radio(
309
+ [Menu["Yes"], Menu["No"]], value=None, label="", visible=True
310
+ ),
311
+ gr.Textbox(
312
+ Config[list(Config.keys())[state_int]], visible=True, label=""
313
+ ),
314
+ gr.Button(Menu["Give_up"], visible=False),
315
+ gr.Textbox(visible=False),
316
+ gr.Image(interactive=False, visible=False),
317
+ gr.Textbox(visible=False),
318
+ gr.Button(visible=False),
319
+ gr.Markdown(visible=False),
320
+ gr.Button(visible=False),
321
+ secret_word_used,
322
+ ]
323
+ )
324
+
325
+ # Define UI components for the second state, Depending on the answer, show the rules or keep going
326
+ elif state_int == 2:
327
+ if radio == Menu["No"]:
328
+ output = [
329
+ dif_state,
330
+ gr.Button("Introducir", visible=True),
331
+ gr.Radio(visible=False),
332
+ gr.Textbox(
333
+ Config[list(Config.keys())[state_int]], visible=True, label=""
334
+ ),
335
+ gr.Button(Menu["Give_up"], visible=False),
336
+ gr.Textbox(visible=False),
337
+ gr.Image(interactive=False, visible=False),
338
+ gr.Textbox(visible=False),
339
+ gr.Button(visible=False),
340
+ gr.Markdown(visible=False),
341
+ gr.Button(visible=False),
342
+ secret_word_used,
343
+ ]
344
+
345
+ else:
346
+ output.extend(
347
+ [
348
+ gr.Button(Menu["Next"], visible=True),
349
+ gr.Radio(visible=False),
350
+ gr.Textbox(
351
+ Config[list(Config.keys())[state_int]],
352
+ visible=True,
353
+ label="",
354
+ ),
355
+ gr.Button(Menu["Give_up"], visible=False),
356
+ gr.Textbox(visible=False),
357
+ gr.Image(interactive=False, visible=False),
358
+ gr.Textbox(visible=False),
359
+ gr.Button(visible=False),
360
+ gr.Markdown(visible=False),
361
+ gr.Button(visible=False),
362
+ secret_word_used,
363
+ ]
364
+ )
365
+
366
+ # Define UI components for the difficulty state, ask the user to select the difficulty level
367
+ elif state_int == dif_state:
368
+ output.extend(
369
+ [
370
+ gr.Button(Menu["Start"], visible=True, variant="primary"),
371
+ gr.Radio(visible=False),
372
+ gr.Textbox(
373
+ Config[list(Config.keys())[state_int]], visible=True, label=""
374
+ ),
375
+ gr.Button(Menu["Give_up"], visible=False),
376
+ gr.Textbox(visible=False),
377
+ gr.Image(interactive=False, visible=False),
378
+ gr.Textbox(visible=False),
379
+ gr.Button(visible=False),
380
+ gr.Markdown(visible=False),
381
+ gr.Button(visible=False),
382
+ secret_word_used,
383
+ ]
384
+ )
385
+
386
+ # Define UI components for the difficulty state + 2, play the game based on the selected difficulty level and prepare the game for the word guessing
387
+ elif state_int == dif_state + 1:
388
+
389
+ game_instances[request.session_hash].prepare_game(
390
+ secret_word_used, 2 if hints_enabled else 4
391
+ )
392
+
393
+ output.extend(
394
+ [
395
+ gr.Button(Menu["Send"], visible=True, variant="primary"),
396
+ gr.Radio(label="", visible=False),
397
+ gr.Textbox(visible=False, label=""),
398
+ gr.Button(visible=False, variant="stop"),
399
+ gr.Textbox(
400
+ value="",
401
+ visible=True,
402
+ autofocus=True,
403
+ placeholder=Menu["New_word"],
404
+ ),
405
+ gr.Image(interactive=False, visible=False),
406
+ gr.Textbox(visible=False),
407
+ gr.Button(visible=False),
408
+ gr.Markdown(visible=False),
409
+ gr.Button(visible=False),
410
+ secret_word_used + 1,
411
+ ]
412
+ )
413
+
414
+ # Define UI components for the state greater than the difficulty state + 2, play the game and provide feedback based on the user input
415
+ elif state_int > dif_state + 1:
416
+
417
+ # Send the user input to the game and get the feedback from the game
418
+ feed = game_instances[request.session_hash].play_game(inp, model_class)
419
+
420
+ # Check if the feedback contains the ranking information and process it
421
+ feedback_trim = feed.split("[rank]")
422
+ if len(feedback_trim) > 1:
423
+ ranking_vis = True
424
+ ranking_md = convert_to_markdown_centered(feedback_trim[1])
425
+
426
+ else:
427
+ ranking_vis = False
428
+ ranking_md = ""
429
+
430
+ # Check if the feedback contains a hint, win, or lose message
431
+ feedback = feedback_trim[0].split("[hint]")
432
+ win = feedback_trim[0].split("[win]")
433
+ lose = feedback_trim[0].split("[lose]")
434
+
435
+ # Check if the feedback contains a hint message
436
+ if len(feedback) > 1:
437
+ hint = True
438
+ hint_out = feedback[1]
439
+ feedback = feedback[0]
440
+ else:
441
+ hint = False
442
+ feedback = feedback[0]
443
+
444
+ # Check if the feedback contains a win or lose message and process it
445
+ if len(win) > 1 or len(lose) > 1:
446
+
447
+ # Check if the user won the game
448
+ won = True if len(win) > 1 else False
449
+
450
+ # Get the curiosity message from the game
451
+ curiosity = game_instances[request.session_hash].curiosity()
452
+
453
+ # Define the output components for the win or lose state
454
+ output.extend(
455
+ [
456
+ gr.Button(Menu["Send"], visible=False, variant="primary"),
457
+ gr.Radio(label="", visible=False),
458
+ gr.Textbox(win[1] if won else lose[1], visible=True, label=""),
459
+ gr.Button(visible=False, variant="stop"),
460
+ gr.Textbox(
461
+ value="", visible=False, placeholder=Menu["New_word"]
462
+ ),
463
+ gr.Image(
464
+ logo_win_path if won else logo_path,
465
+ interactive=False,
466
+ visible=True,
467
+ ),
468
+ gr.Textbox(curiosity, visible=True, label=Menu["Curiosity"]),
469
+ gr.Button(Menu["Play_again"], variant="primary", visible=True),
470
+ gr.Markdown(visible=False),
471
+ gr.Button(visible=True),
472
+ secret_word_used,
473
+ ]
474
+ )
475
+
476
+ return output
477
+
478
+ # Define the output components for the feedback and keep playing
479
+ output.extend(
480
+ [
481
+ gr.Button(Menu["Send"], visible=True, variant="primary"),
482
+ gr.Radio(label="", visible=False),
483
+ gr.Textbox(feedback, visible=True, label=""),
484
+ gr.Button(visible=True, variant="stop"),
485
+ gr.Textbox(value="", visible=True, placeholder=Menu["New_word"]),
486
+ gr.Image(logo_path, interactive=False, visible=False),
487
+ gr.Textbox(hint_out if hint else "", visible=hint, label="Pista"),
488
+ gr.Button(visible=False),
489
+ gr.Markdown(ranking_md, visible=ranking_vis),
490
+ gr.Button(visible=False),
491
+ secret_word_used,
492
+ ]
493
+ )
494
+
495
+ # Define UI components for the rest of the states, state for showing basic text to the user
496
+ else:
497
+ output.extend(
498
+ [
499
+ gr.Button(Menu["Next"], visible=True),
500
+ gr.Radio(label="", visible=False),
501
+ gr.Textbox(
502
+ Config[list(Config.keys())[state_int]], visible=True, label=""
503
+ ),
504
+ gr.Button("Pista", visible=False),
505
+ gr.Textbox(visible=False),
506
+ gr.Image(interactive=False, visible=False),
507
+ gr.Textbox(visible=False),
508
+ gr.Button(visible=False),
509
+ gr.Markdown(visible=False),
510
+ gr.Button(visible=False),
511
+ secret_word_used,
512
+ ]
513
+ )
514
+
515
+ # Return the output components
516
+ return output
517
+
518
+ def commit_data(request: gr.Request):
519
+ session_id = game_instances[request.session_hash].get_session_id()
520
+ session_files = [f for f in os.listdir(plays_path) if f.startswith(session_id)]
521
+ combined_data = []
522
+
523
+ for file_name in session_files:
524
+ with open(os.path.join(plays_path, file_name), "r") as file:
525
+ combined_data.append(json.load(file))
526
+ os.remove(os.path.join(plays_path, file_name))
527
+
528
+ combined_file_path = os.path.join(plays_path, f"{session_id}.json")
529
+ with open(combined_file_path, "w") as combined_file:
530
+ json.dump(combined_data, combined_file, indent=4)
531
+
532
+ scheduler.push_to_hub()
533
+
534
+ if os.path.exists(combined_file_path):
535
+ os.remove(combined_file_path)
536
+
537
+ return [
538
+ gr.Button(visible=False),
539
+ gr.Textbox(visible=False),
540
+ gr.Textbox(visible=False),
541
+ gr.Button(
542
+ "Rellenar cuestionario",
543
+ variant="primary",
544
+ link="https://docs.google.com/forms/d/e/1FAIpQLSdGkCMsjk4r5Zvm1hZuebbPRK5LVc_aivbb8e9WrblX_yTHvg/viewform?usp=pp_url&entry.1459162177="
545
+ + session_id,
546
+ visible=True,
547
+ ),
548
+ ]
549
+
550
+ # Define the UI layout for the gam
551
+ img = gr.Image(logo_path, height=430, interactive=False, visible=True)
552
+ ranking = gr.Markdown(visible=False)
553
+
554
+ with gr.Row():
555
+ out = gr.Textbox(visible=False, placeholder=Config[list(Config.keys())[0]])
556
+ hint_out = gr.Textbox(visible=False)
557
+
558
+ radio = gr.Radio(visible=False)
559
+
560
+ with gr.Row():
561
+ inp = gr.Textbox(visible=False, interactive=True, label="")
562
+ but = gr.Button(Menu["Start"])
563
+ give_up = gr.Button("Pista", visible=False)
564
+ reload = gr.Button(Menu["Play_again"], visible=False)
565
+ finish = gr.Button(
566
+ "Terminar",
567
+ variant="stop",
568
+ # link="https://docs.google.com/forms/d/e/1FAIpQLSd0z8nI4hhOSR83yPIw_bR3KkSt25Lsq0ZXG1pZnkldeoceqA/viewform?usp=pp_url&entry.327829192=Condici%C3%B3n+1",
569
+ visible=False,
570
+ )
571
+
572
+ timer_delete = gr.Timer(value=30)
573
+
574
+ # Define the UI events for the game
575
+
576
+ # Define events that trigger the game state change
577
+ timer_delete.tick(remove_game_instance, inputs=[gr.State(True)])
578
+ inp.submit(
579
+ change,
580
+ inputs=[state, inp],
581
+ outputs=[state, inp],
582
+ concurrency_limit=5,
583
+ )
584
+ but.click(
585
+ change,
586
+ inputs=[state, inp],
587
+ outputs=[state, inp],
588
+ concurrency_limit=5,
589
+ )
590
+ give_up.click(
591
+ change,
592
+ inputs=[
593
+ state,
594
+ gr.Textbox("give_up", visible=False, interactive=True, label=""),
595
+ ],
596
+ outputs=[state, inp],
597
+ concurrency_limit=5,
598
+ )
599
+ radio.input(
600
+ change,
601
+ inputs=[state, inp],
602
+ outputs=[state, inp],
603
+ concurrency_limit=5,
604
+ )
605
+ finish.click(
606
+ commit_data, outputs=[reload, out, hint_out, finish]
607
+ ) # Define events that trigger the game reset
608
+ reload.click(
609
+ reset,
610
+ inputs=[gr.State(False)],
611
+ outputs=[state, out, inp, img, but, radio, hint_out, reload, header, finish],
612
+ )
613
+ demo.load(
614
+ reset,
615
+ inputs=[gr.State(True)],
616
+ outputs=[state, out, inp, img, but, radio, hint_out, reload, header, finish],
617
+ )
618
+ demo.unload(remove_game_instance)
619
+
620
+ # Define events that trigger the game state update
621
+ state.change(
622
+ update,
623
+ inputs=[state, radio, inp, hint, secret_word_used],
624
+ outputs=[
625
+ state,
626
+ but,
627
+ radio,
628
+ out,
629
+ give_up,
630
+ inp,
631
+ img,
632
+ hint_out,
633
+ reload,
634
+ ranking,
635
+ finish,
636
+ secret_word_used,
637
+ ],
638
+ )
639
+
640
+ if __name__ == "__main__":
641
+ demo.launch(debug=True)
game.py ADDED
@@ -0,0 +1,417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3.10
2
+
3
+ # """
4
+ # Semantrix Game Module
5
+
6
+ # 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.
7
+
8
+ # Classes:
9
+ # Semantrix: A class that implements the Semantrix word guessing game.
10
+ # Semantrix.DictWrapper: A helper class to wrap configuration dictionaries.
11
+
12
+ # Functions:
13
+ # __init__(self, lang=0, model_type="SentenceTransformer"): Initializes the Semantrix game with the specified language and model type.
14
+ # prepare_game(self, difficulty): Prepares the game with the selected difficulty level.
15
+ # gen_rank(self, repeated): Generates the ranking file based on the scores.
16
+ # play_game(self, word): Plays the game with the selected word and returns feedback.
17
+ # curiosity(self): Generates a curiosity hint about the secret word once the game is over.
18
+
19
+ # Attributes:
20
+ # model (KeyedVectors): The word embeddings model.
21
+ # config_file_path (str): Path to the configuration file.
22
+ # secret_file_path (str): Path to the secret words file.
23
+ # data_path (str): Path to the data directory.
24
+ # Config_full (dict): Full configuration data.
25
+ # secret (dict): Secret words data.
26
+ # lang (int): Language of the game (0 for Spanish, 1 for English).
27
+ # model_type (str): Type of the model ("word2vec" or "SentenceTransformer").
28
+ # Config (DictWrapper): Configuration data for the selected language.
29
+ # secret_dict (dict): Secret words for the selected language.
30
+ # secret_list (list): List of secret words for the selected difficulty.
31
+ # words (list): List of words guessed by the player.
32
+ # scores (list): List of scores for the guessed words.
33
+ # win (bool): Indicates if the player has won the game.
34
+ # n (int): Number of hints given.
35
+ # recent_hint (int): Counter for recent hints.
36
+ # f_dev_avg (float): Moving average of the tendency slope.
37
+ # last_hint (int): Index of the last hint given.
38
+ # difficulty (int): Difficulty level of the game.
39
+ # """
40
+
41
+ import os
42
+ import sys
43
+ import json
44
+ import uuid
45
+ import random
46
+ from datetime import datetime
47
+ import time
48
+ from tqdm import tqdm
49
+ import numpy as np
50
+ from gensim.models import KeyedVectors
51
+ from hints import curiosity, hint
52
+ from tracking import (
53
+ calculate_moving_average,
54
+ calculate_tendency_slope,
55
+ )
56
+ from sentence_transformers import SentenceTransformer, util
57
+ import warnings
58
+ from huggingface_hub import snapshot_download
59
+
60
+
61
+ warnings.filterwarnings(action="ignore", category=UserWarning, module="gensim")
62
+
63
+
64
+ class Model_class:
65
+
66
+ base_path = os.path.dirname(os.path.abspath(__file__))
67
+
68
+ def __init__(self, lang=0, model_type="SentenceTransformer"):
69
+
70
+ if model_type == "SentenceTransformer":
71
+ repo_url = "Jsevisal/strans_models"
72
+
73
+ else:
74
+ repo_url = "Jsevisal/w2v_models"
75
+
76
+ # Check if the model exists, clone it if it doesn't
77
+ if not os.path.exists(
78
+ os.path.join(self.base_path, "config/strans_models/")
79
+ ) or not os.path.exists(os.path.join(self.base_path, "config/w2v_models/")):
80
+ model_path = snapshot_download(repo_id=repo_url)
81
+
82
+ if lang == 1:
83
+ if model_type == "word2vec":
84
+ self.model = KeyedVectors.load(
85
+ os.path.join(model_path, "eng_w2v_model"),
86
+ mmap="r",
87
+ )
88
+ elif model_type == "SentenceTransformer":
89
+ self.model = KeyedVectors.load(
90
+ os.path.join(model_path, "eng_strans_model"),
91
+ mmap="r",
92
+ )
93
+
94
+ else:
95
+ if model_type == "word2vec":
96
+ self.model = KeyedVectors.load(
97
+ os.path.join(model_path, "esp_w2v_model"),
98
+ mmap="r",
99
+ )
100
+
101
+ elif model_type == "SentenceTransformer":
102
+ self.model = KeyedVectors.load(
103
+ os.path.join(model_path, "esp_strans_model"),
104
+ mmap="r",
105
+ )
106
+
107
+ self.model_st = SentenceTransformer(
108
+ "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
109
+ )
110
+
111
+
112
+ # Define the class Semantrix
113
+ class Semantrix:
114
+
115
+ # Define the paths for the configuration files and the data
116
+ base_path = os.path.dirname(os.path.abspath(__file__))
117
+ config_file_path = os.path.join(base_path, "config/")
118
+ config_file_path = os.path.join(base_path, "config/lang.json")
119
+ secret_file_path = os.path.join(base_path, "config/secret.json")
120
+ data_path = os.path.join(base_path, "data/")
121
+
122
+ # Define the class DictWrapper to store the configuration data
123
+ class DictWrapper:
124
+ def __init__(self, data_dict):
125
+ self.__dict__.update(data_dict)
126
+
127
+ # 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
128
+ def __init__(self, lang=0, model_type="SentenceTransformer", session_hash=None):
129
+
130
+ # Load the configuration files
131
+ with open(self.config_file_path, "r") as file:
132
+ self.Config_full = json.load(file)
133
+
134
+ # Load the secret file where the secret words are stored
135
+ with open(self.secret_file_path, "r") as file:
136
+ self.secret = json.load(file)
137
+
138
+ # Set the language of the game
139
+ self.lang = lang
140
+
141
+ self.session_id = str(uuid.uuid4().hex)
142
+
143
+ # Set the model type
144
+ self.model_type = model_type
145
+
146
+ self.session_hash = session_hash
147
+ self.ranking_path = "rankings/ranking_" + str(self.session_hash) + ".txt"
148
+
149
+ self.ranking_data = []
150
+ self.ranking_msg = ""
151
+
152
+ if lang == 1:
153
+ self.Config = self.DictWrapper(self.Config_full["ENG"]["Game"])
154
+ self.secret_dict = self.secret["ENG"]
155
+ self.secret_list = self.secret_dict["basic"]
156
+ else:
157
+ self.Config = self.DictWrapper(self.Config_full["SPA"]["Game"])
158
+ self.secret_dict = self.secret["SPA"]
159
+ self.secret_list = self.secret_dict["basic"]
160
+
161
+ # Create the ranking file
162
+ with open(self.data_path + self.ranking_path, "w+") as file:
163
+ file.write("---------------------------")
164
+
165
+ def reset_game(self):
166
+ self.session_id = str(uuid.uuid4().hex)
167
+ # Load the secret file where the secret words are stored
168
+ with open(self.secret_file_path, "r") as file:
169
+ self.secret = json.load(file)
170
+ self.secret_dict = self.secret["SPA"]
171
+ self.secret_list = self.secret_dict["basic"]
172
+
173
+ def generate_gensim_model(self, model_class, batch_size=32):
174
+ from tqdm import tqdm
175
+
176
+ self.model_trans = KeyedVectors(768)
177
+
178
+ self.model_trans.init_sims(replace=True)
179
+ words = list(model_class.model.key_to_index.keys())
180
+ num_batches = (
181
+ len(words) + batch_size - 1
182
+ ) // batch_size # Calculate the number of batches
183
+
184
+ for batch_index in tqdm(range(num_batches)):
185
+ # Get the batch of words
186
+ start_index = batch_index * batch_size
187
+ end_index = min(start_index + batch_size, len(words))
188
+ batch_words = words[start_index:end_index]
189
+
190
+ # Encode the batch of words
191
+ encoded_vectors = model_class.model_st.encode(
192
+ batch_words,
193
+ convert_to_tensor=True,
194
+ prompt="Encuentra el valor semántico de la palabra: ",
195
+ ).tolist()
196
+
197
+ # # Add vectors to the model
198
+ self.model_trans.add_vectors(batch_words, encoded_vectors)
199
+
200
+ self.model_trans.save("config/strans_models/esp_strans_model_prompt")
201
+
202
+ # Define the function to prepare the game with the selected difficulty
203
+ def prepare_game(self, secret_word_used, difficulty):
204
+
205
+ # Set the secret list depending on the difficulty
206
+ self.secret = self.secret_list[secret_word_used]
207
+ self.secret = self.secret.lower()
208
+
209
+ self.init_time = time.time()
210
+
211
+ # Store the secret word in the words list
212
+ self.words = [self.Config.secret_word]
213
+
214
+ # Store the score in the scores list
215
+ self.scores = [10]
216
+
217
+ # Initialize the game variables
218
+ self.win = False
219
+ self.n = 0
220
+ self.recent_hint = 0
221
+ self.f_dev_avg = 0
222
+ self.last_hint = -1
223
+ self.difficulty = difficulty
224
+
225
+ # Set the number of hints depending on the difficulty
226
+ if self.difficulty == 1:
227
+ self.n = 3
228
+
229
+ # Define the function to generate the ranking file
230
+ def gen_rank(self, repeated):
231
+ ascending_indices = np.argsort(self.scores)
232
+ descending_indices = list(ascending_indices[::-1])
233
+ self.ranking_data.clear()
234
+ k = len(self.words) - 1
235
+ if repeated != -1:
236
+ k = repeated
237
+
238
+ self.ranking_data.append(["#" + str(k), self.words[k], self.scores[k]])
239
+
240
+ self.ranking_data.append("---------------------------")
241
+ for i in descending_indices:
242
+ if i == 0:
243
+ continue
244
+ self.ranking_data.append(["#" + str(i), self.words[i], self.scores[i]])
245
+
246
+ with open(self.data_path + self.ranking_path, "w+") as file:
247
+ for item in self.ranking_data:
248
+ file.write("%s\n" % item)
249
+
250
+ self.ranking_msg = ""
251
+ for item in self.ranking_data:
252
+ self.ranking_msg += f"{item}\n"
253
+
254
+ # Define the function to play the game with the selected word
255
+ def play_game(self, word, model_class):
256
+
257
+ # Convert the word to lowercase
258
+ word = word.lower().strip()
259
+
260
+ # Check if the user wants to give up
261
+ if word == "give_up":
262
+ text = (
263
+ "[lose]"
264
+ + str(self.Config.Feedback_9)
265
+ + self.secret
266
+ + "\n\n"
267
+ + self.Config.Feedback_10
268
+ )
269
+ return text
270
+
271
+ # Check if the word is repeated
272
+ if word in self.words:
273
+ repeated = self.words.index(word)
274
+ else:
275
+ repeated = -1
276
+ self.words.append(word)
277
+
278
+ # Check if the word is in the model already
279
+ if word not in model_class.model.key_to_index.keys():
280
+ # If the word is not in the model, remove it from the words list and provide feedback
281
+ self.words.pop(len(self.words) - 1)
282
+ feedback = (
283
+ "I don't know that word. Try another word."
284
+ if self.lang == 1
285
+ else "No conozco esa palabra. Prueba con otra palabra."
286
+ )
287
+
288
+ feedback += "[rank]" + self.ranking_msg if len(self.words) > 1 else "\n\n"
289
+ return feedback
290
+
291
+ similarity = model_class.model.similarity(self.secret, word)
292
+
293
+ if self.model_type == "word2vec":
294
+ score = np.round(similarity * 10, 2)
295
+ else:
296
+ # log_similarity = np.log10(similarity * 10) if np.any(similarity > 0) else 0
297
+ # score = np.round(
298
+ # np.interp(
299
+ # log_similarity,
300
+ # [0, np.log10(10)],
301
+ # [0, 10],
302
+ # ),
303
+ # 2,
304
+ # )
305
+ score = np.round(similarity * 10, 2)
306
+
307
+ # Remove the word from the score list if it is repeated
308
+ if repeated == -1:
309
+ self.scores.append(score)
310
+
311
+ # Generate the feedback message depending on the score
312
+ if score <= 2.5:
313
+ feedback = self.Config.Feedback_0 + str(score)
314
+ elif score > 2.5 and score <= 6.0:
315
+ feedback = self.Config.Feedback_1 + str(score)
316
+ elif score > 6.0 and score <= 7.0:
317
+ feedback = self.Config.Feedback_2 + str(score)
318
+ elif score > 7.0 and score <= 8:
319
+ feedback = self.Config.Feedback_3 + str(score)
320
+ elif score > 8 and score <= 9.0:
321
+ feedback = self.Config.Feedback_4 + str(score)
322
+ elif score > 9.0 and score < 10.0:
323
+ feedback = self.Config.Feedback_5 + str(score)
324
+ # If the score is 10, the user wins the game
325
+ else:
326
+ self.win = True
327
+ feedback = "[win]" + self.Config.Feedback_8
328
+ self.words[0] = self.secret
329
+ self.words.pop(len(self.words) - 1)
330
+ self.scores.pop(len(self.scores) - 1)
331
+
332
+ # Generate the feedback message depending on the score and the previous score
333
+ if score > self.scores[len(self.scores) - 2] and self.win == False:
334
+ feedback += "\n" + self.Config.Feedback_6
335
+ elif score < self.scores[len(self.scores) - 2] and self.win == False:
336
+ feedback += "\n" + self.Config.Feedback_7
337
+
338
+ ## Hint generation
339
+ # If the difficulty is not 4, calculate the moving average of the scores and the tendency slope
340
+ if self.difficulty != 4 and len(self.scores) > 1:
341
+ mov_avg = calculate_moving_average(self.scores[1:], 5)
342
+
343
+ # 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
344
+ if len(mov_avg) > 1 and self.win == False:
345
+ f_dev = calculate_tendency_slope(mov_avg)
346
+ f_dev_avg = calculate_moving_average(f_dev, 3)
347
+
348
+ # If the tendency slope is negative and the hint has not been given recently (at least three rounds earlier), generate a hint
349
+ if f_dev_avg[len(f_dev_avg) - 1] < 0 and self.recent_hint == 0:
350
+
351
+ # Generate a random hint intro from the hint list
352
+ i = random.randint(0, len(self.Config.hint_intro) - 1)
353
+ feedback += "\n\n[hint]" + self.Config.hint_intro[i]
354
+
355
+ # Generate a dynamic hint
356
+ hint_text, self.n, self.last_hint = hint(
357
+ self.secret,
358
+ self.n,
359
+ model_class.model_st,
360
+ self.last_hint,
361
+ self.lang,
362
+ (
363
+ self.DictWrapper(self.Config_full["ENG"]["Hint"])
364
+ if self.lang == 1
365
+ else self.DictWrapper(self.Config_full["SPA"]["Hint"])
366
+ ),
367
+ )
368
+ feedback += "\n" + hint_text
369
+ self.recent_hint = 3
370
+
371
+ if self.recent_hint != 0:
372
+ self.recent_hint -= 1
373
+
374
+ # Generate the ranking file
375
+ self.gen_rank(repeated)
376
+
377
+ # Add the ranking file to the feedback message
378
+ feedback += "[rank]" + self.ranking_msg if len(self.words) > 1 else "\n\n"
379
+
380
+ # Return the feedback message
381
+ return feedback
382
+
383
+ # Define the function to generate a curiosity hint once the game is over
384
+ def curiosity(self):
385
+
386
+ # Generate a curiosity aboyt the secret word
387
+ feedback = curiosity(
388
+ self.secret,
389
+ (
390
+ self.DictWrapper(self.Config_full["ENG"]["Hint"])
391
+ if self.lang == 1
392
+ else self.DictWrapper(self.Config_full["SPA"]["Hint"])
393
+ ),
394
+ )
395
+
396
+ # Save the ranking file with the plays of the user if the user wins
397
+ with open(self.data_path + self.ranking_path, "r") as original_file:
398
+ file_content = original_file.readlines()[2:]
399
+ new_file_name = f"{self.session_id}-{self.secret}.json"
400
+ play_data = {
401
+ "session_id": self.session_id,
402
+ "datetime": str(datetime.now()),
403
+ "time": time.time() - self.init_time,
404
+ "data": file_content,
405
+ "win": self.win,
406
+ "secret": self.secret,
407
+ "number_of_hints": self.n,
408
+ }
409
+
410
+ with open(self.data_path + "plays/" + new_file_name, "w") as new_file:
411
+ json.dump(play_data, new_file, indent=4)
412
+
413
+ # Return the feedback message
414
+ return feedback
415
+
416
+ def get_session_id(self):
417
+ return self.session_id
gen_model.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %%
2
+ from game import Semantrix, Model_class
3
+
4
+ # %%
5
+ model = "word2vec"
6
+ model_class = Model_class(lang=0, model_type=model)
7
+
8
+ sem = Semantrix(lang=1, model_type="SentenceTransformer")
9
+
10
+ # %%
11
+ sem.generate_gensim_model(batch_size=128, model_class=model_class)
12
+
13
+ # %%
hints.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # """
2
+ # This module provides functions to generate dynamic hints and curiosities about a secret word using llms.
3
+
4
+ # Functions:
5
+ # hint(secret, n, model, last_hint, lang, Config):
6
+ # Generates a dynamic hint based on the secret word and the number of hints given.
7
+ # Parameters:
8
+ # secret (str): The secret word.
9
+ # n (int): The number of hints already given.
10
+ # model: The sentence transformer model used for encoding.
11
+ # last_hint (int): The index of the last hint given.
12
+ # lang (int): The language code (0 for Spanish, 1 for English).
13
+ # Config: Configuration object containing hint templates.
14
+ # Returns:
15
+ # tuple: A tuple containing the generated hint (str), the updated number of hints (int), and the index of the last hint given (int).
16
+
17
+ # curiosity(secret, Config):
18
+ # Generates a curiosity about the secret word.
19
+ # Parameters:
20
+ # secret (str): The secret word.
21
+ # Config: Configuration object containing the curiosity template.
22
+ # Returns:
23
+ # str: The generated curiosity.
24
+
25
+ # ireplace(old, new, text):
26
+ # Replaces all occurrences of a substring in a string, case-insensitively.
27
+ # Parameters:
28
+ # old (str): The substring to be replaced.
29
+ # new (str): The substring to replace with.
30
+ # text (str): The original string.
31
+ # Returns:
32
+ # str: The modified string with all occurrences of the old substring replaced by the new substring.
33
+ # """
34
+
35
+ import random
36
+ import openai
37
+ from sentence_transformers import util
38
+
39
+ gpt_model = "gpt-3.5-turbo"
40
+
41
+
42
+ # Dynamic hint function that returns a hint based on the secret word and the number of hints given
43
+ def hint(secret, n, model, last_hint, lang, Config):
44
+
45
+ # Initialize hint variable
46
+ hint = ""
47
+
48
+ # Advanced hints
49
+ if n >= 3:
50
+ j = random.randint(0, 2)
51
+ while j == last_hint:
52
+ j = random.randint(0, 2)
53
+
54
+ # Advanced hint 1: Definition of the secret word
55
+ if j == 0:
56
+ response = openai.chat.completions.create(
57
+ model=gpt_model,
58
+ messages=[
59
+ {
60
+ "role": "user",
61
+ "content": Config.hint_0_0 # type: ignore
62
+ + secret
63
+ + Config.hint_0_1 # type: ignore
64
+ + secret
65
+ + Config.hint_0_2, # type: ignore
66
+ }
67
+ ],
68
+ temperature=1,
69
+ max_tokens=256,
70
+ top_p=1,
71
+ frequency_penalty=0.5,
72
+ presence_penalty=0,
73
+ )
74
+ output = str(response.choices[0].message.content)
75
+ output = output.replace('"', "").replace("'", "")
76
+
77
+ # Replace the secret word with "La palabra secreta" or "The secret word" just in case the model uses the secret word in the definition
78
+ if lang == 0:
79
+ output = ireplace("la " + secret, "La palabra secreta", output)
80
+ output = ireplace("las " + secret, "La palabra secreta", output)
81
+ output = ireplace("el " + secret, "La palabra secreta", output)
82
+ output = ireplace("los " + secret, "La palabra secreta", output)
83
+ output = ireplace("un " + secret, "La palabra secreta", output)
84
+ output = ireplace("una " + secret, "La palabra secreta", output)
85
+ output = ireplace("unos " + secret, "La palabra secreta", output)
86
+ output = ireplace("unas " + secret, "La palabra secreta", output)
87
+ elif lang == 1:
88
+ output = ireplace("the " + secret, "The secret word", output)
89
+ output = ireplace("a " + secret, "The secret word", output)
90
+
91
+ hint += Config.hint_0_3 + output # type: ignore
92
+ last_hint = 0
93
+
94
+ # Advanced hint 2: Representation of the secret word with emojis
95
+ elif j == 1:
96
+ response = openai.chat.completions.create(
97
+ model=gpt_model,
98
+ messages=[
99
+ {
100
+ "role": "user",
101
+ "content": Config.hint_1_0 + secret + Config.hint_1_1, # type: ignore
102
+ }
103
+ ],
104
+ temperature=1,
105
+ max_tokens=256,
106
+ top_p=1,
107
+ frequency_penalty=0,
108
+ presence_penalty=0,
109
+ )
110
+ output = str(response.choices[0].message.content)
111
+ hint += Config.hint_1_2 + output # type: ignore
112
+ last_hint = 1
113
+
114
+ # Advanced hint 3: Poem about the secret word
115
+ elif j == 2:
116
+ response = openai.chat.completions.create(
117
+ model=gpt_model,
118
+ messages=[
119
+ {
120
+ "role": "user",
121
+ "content": Config.hint_2_0 + secret + Config.hint_2_1, # type: ignore
122
+ }
123
+ ],
124
+ temperature=1,
125
+ max_tokens=256,
126
+ top_p=1,
127
+ frequency_penalty=0,
128
+ presence_penalty=0,
129
+ )
130
+ output = str(response.choices[0].message.content)
131
+
132
+ hint += Config.hint_2_2 + output # type: ignore
133
+
134
+ last_hint = 2
135
+ # Initial hints
136
+ else:
137
+ j = random.randint(3, 4)
138
+ while j == last_hint:
139
+ j = random.randint(3, 4)
140
+
141
+ # Initial hint 1: Rank of four words related to the secret word
142
+ if j == 3:
143
+ words = []
144
+ response = openai.chat.completions.create(
145
+ model=gpt_model,
146
+ messages=[
147
+ {
148
+ "role": "user",
149
+ "content": Config.hint_3_0, # type: ignore
150
+ }
151
+ ],
152
+ temperature=1.25,
153
+ max_tokens=256,
154
+ top_p=1,
155
+ frequency_penalty=0,
156
+ presence_penalty=0,
157
+ )
158
+ output = str(response.choices[0].message.content)
159
+ output = (output.replace(" ", "").replace(".", "")).lower()
160
+ words.extend(output.strip().split(","))
161
+ response = openai.chat.completions.create(
162
+ model=gpt_model,
163
+ messages=[
164
+ {
165
+ "role": "user",
166
+ "content": Config.hint_3_1 # type: ignore
167
+ + secret
168
+ + Config.hint_3_2, # type: ignore
169
+ }
170
+ ],
171
+ temperature=1.1,
172
+ max_tokens=256,
173
+ top_p=1,
174
+ frequency_penalty=0,
175
+ presence_penalty=0,
176
+ )
177
+ output = str(response.choices[0].message.content)
178
+ output = (output.replace(".", "")).lower()
179
+ words.append(output) # type: ignore
180
+ random.shuffle(words)
181
+ sentences1 = [secret, secret, secret, secret]
182
+ sentences2 = words
183
+ embeddings1 = model.encode(sentences1, convert_to_tensor=True)
184
+ embeddings2 = model.encode(sentences2, convert_to_tensor=True)
185
+
186
+ cosine_scores = util.cos_sim(embeddings1, embeddings2)
187
+ scores = cosine_scores[0].tolist()
188
+ sum_scores = sum(scores)
189
+ normalized_scores = [round(score * 100 / sum_scores, 1) for score in scores]
190
+
191
+ hint += Config.hint_3_3 # type: ignore
192
+
193
+ max_len = -1
194
+ for ele in words:
195
+ if len(ele) > max_len:
196
+ max_len = len(ele)
197
+ longest_word = ele
198
+
199
+ for i in range(len(words)):
200
+
201
+ word_hint = words[i].ljust(len(longest_word) + 1)
202
+ hint += (
203
+ # word_hint[: len(longest_word)]
204
+ word_hint
205
+ + "|"
206
+ + ("🟩") * round(normalized_scores[i] * 0.2)
207
+ + " "
208
+ + str(normalized_scores[i])
209
+ + "%\n"
210
+ )
211
+ last_hint = 3
212
+
213
+ # Initial hint 2: Film representation with emojis with the secret word
214
+ elif j == 4:
215
+ response = openai.chat.completions.create(
216
+ model=gpt_model,
217
+ messages=[
218
+ {
219
+ "role": "user",
220
+ "content": Config.hint_4_0 # type: ignore
221
+ + secret
222
+ + Config.hint_4_1, # type: ignore
223
+ }
224
+ ],
225
+ temperature=1,
226
+ max_tokens=256,
227
+ top_p=1,
228
+ frequency_penalty=0,
229
+ presence_penalty=0,
230
+ )
231
+ film_title = str(response.choices[0].message.content).replace('"', "")
232
+ response = openai.chat.completions.create(
233
+ model=gpt_model,
234
+ messages=[
235
+ {
236
+ "role": "user",
237
+ "content": Config.hint_4_2 # type: ignore
238
+ + film_title
239
+ + Config.hint_4_3, # type: ignore
240
+ }
241
+ ],
242
+ temperature=1,
243
+ max_tokens=256,
244
+ top_p=1,
245
+ frequency_penalty=0,
246
+ presence_penalty=0,
247
+ )
248
+ output = str(response.choices[0].message.content)
249
+ hint += Config.hint_4_4 + "\n" + output # type: ignore
250
+ last_hint = 4
251
+
252
+ return hint, n + 1, last_hint
253
+
254
+
255
+ # Tell a curiosity about the secret word
256
+ def curiosity(secret, Config):
257
+
258
+ response = openai.chat.completions.create(
259
+ model=gpt_model,
260
+ messages=[
261
+ {
262
+ "role": "user",
263
+ "content": Config.curiosity + secret + '".',
264
+ }
265
+ ],
266
+ temperature=1,
267
+ max_tokens=256,
268
+ top_p=1,
269
+ frequency_penalty=0,
270
+ presence_penalty=0,
271
+ )
272
+ output = str(response.choices[0].message.content)
273
+
274
+ return output
275
+
276
+
277
+ # Replace all occurrences of a substring in a string
278
+ def ireplace(old, new, text):
279
+ idx = 0
280
+ while idx < len(text):
281
+ index_l = text.lower().find(old.lower(), idx)
282
+ if index_l == -1:
283
+ return text
284
+ text = text[:index_l] + new + text[index_l + len(old) :]
285
+ idx = index_l + len(new)
286
+ return text
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gensim==4.3.0
2
+ sentence_transformers==3.4.1
3
+ openai==1.2.3
4
+ gradio==5.21.0
5
+ transformers==4.49.0
6
+ huggingface-hub==0.28.1
7
+ python-multipart==0.0.18
8
+ httpx==0.27.2
tracking.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+
4
+ def calculate_moving_average(scores, window_size):
5
+ # Convert the scores list to a NumPy array for better performance
6
+ scores_array = np.array(scores)
7
+
8
+ # Create an array of rolling windows using the np.convolve function
9
+ moving_averages = np.around(
10
+ np.convolve(scores_array, np.ones(window_size) / window_size, mode="valid"), 2
11
+ )
12
+
13
+ return list(moving_averages)
14
+
15
+
16
+ def calculate_tendency_slope(scores):
17
+ # Convert the scores list to a NumPy array for better performance
18
+ scores_array = np.array(scores)
19
+
20
+ # Calculate the first derivative (slope) of the scores
21
+ derivative = np.around(np.gradient(scores_array), 2)
22
+
23
+ return list(derivative)