181 lines
7.4 KiB
Python
181 lines
7.4 KiB
Python
from flask import Flask, request, jsonify, session
|
|
from gensim.models import KeyedVectors
|
|
import numpy as np
|
|
import random
|
|
import os
|
|
from flask import send_from_directory
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = os.urandom(24)
|
|
|
|
# Utilitaires globaux (en mémoire pour chaque session)
|
|
game_state = {}
|
|
|
|
TOLERANCE = 1e-4
|
|
|
|
# Calculer la similarité cosinus entre deux vecteurs
|
|
def cos(a, b):
|
|
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
|
|
|
|
def is_valid_word(word, lang, valid_words):
|
|
if lang == "fr":
|
|
return word.islower() and all(c.isalpha() for c in word) and word in valid_words and 3 <= len(word) <= 15
|
|
else:
|
|
return word.islower() and word.isalpha() and word in valid_words and 3 <= len(word) <= 15
|
|
|
|
@app.route('/init', methods=['POST'])
|
|
def init_game():
|
|
data = request.json
|
|
first_word = data['first_word'].lower()
|
|
first_score = float(data['first_score']) / 100
|
|
lang = data.get('lang', 'en')
|
|
if lang == "fr":
|
|
tol = 1e-2
|
|
with open("french_words.txt") as f:
|
|
valid_words = set(w.strip() for w in f)
|
|
model_path = "frWac_non_lem_no_postag_no_phrase_200_cbow_cut0.bin"
|
|
model = KeyedVectors.load_word2vec_format(model_path, binary=True)
|
|
else:
|
|
tol = TOLERANCE
|
|
with open("english_words.txt") as f:
|
|
valid_words = set(w.strip() for w in f)
|
|
model_path = "google_news_reduced.kv"
|
|
model = KeyedVectors.load(model_path)
|
|
if first_word not in model:
|
|
return jsonify({'error': 'Mot inconnu du modèle'}), 400
|
|
vec1 = model[first_word]
|
|
all_words = [w for w in model.index_to_key if w != first_word and is_valid_word(w, lang, valid_words)]
|
|
words = [w for w in all_words if abs(cos(vec1, model[w]) - first_score) < tol]
|
|
word_indexes = [all_words.index(w) for w in words]
|
|
session['lang'] = lang
|
|
session['first_word'] = first_word
|
|
session['tol'] = tol
|
|
session['model_path'] = model_path
|
|
session['word_indexes'] = word_indexes
|
|
session['history'] = [] # Liste des {word, score/pass, remaining}
|
|
return jsonify({'count': len(words), 'history': session['history']})
|
|
|
|
@app.route('/next', methods=['POST'])
|
|
def next_word():
|
|
word_indexes = session.get('word_indexes', [])
|
|
if not word_indexes:
|
|
return jsonify({'error': 'Plus de mots'}), 400
|
|
# Recalculer all_words à partir du modèle et du mot de départ
|
|
lang = session.get('lang', 'en')
|
|
model_path = session.get('model_path')
|
|
first_word = session.get('first_word')
|
|
if model_path.endswith('.kv'):
|
|
model = KeyedVectors.load(model_path)
|
|
else:
|
|
model = KeyedVectors.load_word2vec_format(model_path, binary=True)
|
|
if lang == 'fr':
|
|
with open("french_words.txt") as f:
|
|
valid_words = set(w.strip() for w in f)
|
|
else:
|
|
with open("english_words.txt") as f:
|
|
valid_words = set(w.strip() for w in f)
|
|
all_words = [w for w in model.index_to_key if w != first_word and is_valid_word(w, lang, valid_words)]
|
|
idx = random.choice(word_indexes)
|
|
guess = all_words[idx]
|
|
session['current_guess_idx'] = idx
|
|
return jsonify({'guess': guess, 'remaining': len(word_indexes), 'history': session.get('history', [])})
|
|
|
|
@app.route('/score', methods=['POST'])
|
|
def submit_score():
|
|
data = request.json
|
|
score = data.get('score')
|
|
if score is None:
|
|
return jsonify({'error': 'Score manquant'}), 400
|
|
try:
|
|
score = float(score) / 100
|
|
except ValueError:
|
|
return jsonify({'error': 'Score invalide'}), 400
|
|
lang = session.get('lang', 'en')
|
|
tol = session.get('tol', TOLERANCE)
|
|
model_path = session.get('model_path')
|
|
word_indexes = session.get('word_indexes', [])
|
|
current_guess_idx = session.get('current_guess_idx')
|
|
if current_guess_idx is None:
|
|
return jsonify({'error': 'Aucun mot courant'}), 400
|
|
if model_path.endswith('.kv'):
|
|
model = KeyedVectors.load(model_path)
|
|
else:
|
|
model = KeyedVectors.load_word2vec_format(model_path, binary=True)
|
|
first_word = session.get('first_word')
|
|
if lang == 'fr':
|
|
with open("french_words.txt") as f:
|
|
valid_words = set(w.strip() for w in f)
|
|
else:
|
|
with open("english_words.txt") as f:
|
|
valid_words = set(w.strip() for w in f)
|
|
all_words = [w for w in model.index_to_key if w != first_word and is_valid_word(w, lang, valid_words)]
|
|
guess_word = all_words[current_guess_idx]
|
|
vec_guess = model[guess_word]
|
|
new_word_indexes = [i for i in word_indexes if abs(cos(vec_guess, model[all_words[i]]) - score) < tol]
|
|
session['word_indexes'] = new_word_indexes
|
|
history = session.get('history', [])
|
|
history.append({'word': guess_word, 'score': score, 'remaining': len(new_word_indexes)})
|
|
session['history'] = history
|
|
if not new_word_indexes:
|
|
session['current_guess_idx'] = None
|
|
return jsonify({'result': 'Plus aucun mot possible. Terminé.', 'history': history, 'remaining': 0})
|
|
else:
|
|
session['current_guess_idx'] = new_word_indexes[0]
|
|
if len(new_word_indexes) == 1:
|
|
return jsonify({'result': f"Plus qu'un seul mot possible: {all_words[new_word_indexes[0]]}", 'history': history, 'remaining': 1})
|
|
return jsonify({'remaining': len(new_word_indexes), 'history': history})
|
|
|
|
@app.route('/skip', methods=['POST'])
|
|
def skip_word():
|
|
word_indexes = session.get('word_indexes', [])
|
|
current_guess_idx = session.get('current_guess_idx')
|
|
if not word_indexes:
|
|
return jsonify({'result': 'Plus aucun mot possible. Terminé.', 'history': session.get('history', [])})
|
|
if current_guess_idx is None or current_guess_idx >= len(word_indexes):
|
|
# Si aucun mot sélectionné, on prend le premier
|
|
current_guess_idx = word_indexes[0]
|
|
# Recalculer all_words à partir du modèle et du mot de départ
|
|
lang = session.get('lang', 'en')
|
|
model_path = session.get('model_path')
|
|
first_word = session.get('first_word')
|
|
if model_path.endswith('.kv'):
|
|
model = KeyedVectors.load(model_path)
|
|
else:
|
|
model = KeyedVectors.load_word2vec_format(model_path, binary=True)
|
|
if lang == 'fr':
|
|
with open("french_words.txt") as f:
|
|
valid_words = set(w.strip() for w in f)
|
|
else:
|
|
with open("english_words.txt") as f:
|
|
valid_words = set(w.strip() for w in f)
|
|
all_words = [w for w in model.index_to_key if w != first_word and is_valid_word(w, lang, valid_words)]
|
|
# On retire le mot courant de la liste
|
|
try:
|
|
word_indexes.remove(current_guess_idx)
|
|
except ValueError:
|
|
pass
|
|
session['word_indexes'] = word_indexes
|
|
history = session.get('history', [])
|
|
history.append({'word': all_words[current_guess_idx], 'score': 'pass', 'remaining': len(word_indexes)})
|
|
session['history'] = history
|
|
# On avance l'index courant si possible
|
|
if word_indexes:
|
|
session['current_guess_idx'] = word_indexes[0]
|
|
guess = all_words[word_indexes[0]]
|
|
return jsonify({'guess': guess, 'remaining': len(word_indexes), 'history': history})
|
|
else:
|
|
session['current_guess_idx'] = None
|
|
return jsonify({'result': 'Plus aucun mot possible. Terminé.', 'history': history})
|
|
|
|
|
|
# Servir index.html à la racine
|
|
@app.route('/')
|
|
def root():
|
|
return send_from_directory('.', 'index.html')
|
|
|
|
# Servir les fichiers statiques (ex: .txt, .js, .css)
|
|
@app.route('/<path:filename>')
|
|
def static_files(filename):
|
|
return send_from_directory('.', filename)
|
|
|
|
## Le serveur est lancé par gunicorn dans Docker, ne rien lancer ici
|