Spaces:
Paused
Paused
Javierss
commited on
Commit
·
b875699
1
Parent(s):
8295cba
Clone old repo
Browse files- .gitattributes +11 -0
- .gitignore +6 -0
- README.md +106 -4
- app.py +641 -0
- game.py +417 -0
- gen_model.py +13 -0
- hints.py +286 -0
- requirements.txt +8 -0
- 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
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
colorTo: pink
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.22.0
|
|
|
8 |
app_file: app.py
|
9 |
pinned: false
|
|
|
10 |
---
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|