transformed into API

This commit is contained in:
Didictateur 2026-02-02 16:17:03 +01:00
parent 0b2f99b70b
commit 8182bc0eb9
5 changed files with 324 additions and 1 deletions

View file

@ -15,7 +15,7 @@ first_score = float(sys.argv[2]) / 100
# Détection de la langue
lang = sys.argv[3] if len(sys.argv) > 3 else "en"
if lang == "fr":
TOLERANCE = 1e-4
TOLERANCE = 1e-2
with open("french_words.txt") as f:
valid_words = set(w.strip() for w in f)
print("Mode français : dictionnaire français chargé.")

170
cemantle_api.py Normal file
View file

@ -0,0 +1,170 @@
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:
return jsonify({'result': 'Plus aucun mot possible. Terminé.', 'history': history})
if len(new_word_indexes) == 1:
return jsonify({'result': f"Plus qu'un seul mot possible: {all_words[new_word_indexes[0]]}", 'history': history})
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 current_guess_idx is None or not word_indexes:
return jsonify({'error': 'Aucun mot à passer'}), 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)]
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
if not word_indexes:
return jsonify({'result': 'Plus aucun mot possible. Terminé.', 'history': history})
return jsonify({'remaining': len(word_indexes), '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)
if __name__ == '__main__':
app.run(debug=True)

BIN
google_news_reduced.kv Normal file

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cemantle Solver Web</title>
<style>
body { font-family: Arial, sans-serif; margin: 2em; background: #f7f7f7; }
#container { background: #fff; padding: 2em; border-radius: 8px; box-shadow: 0 2px 8px #0001; max-width: 650px; margin: auto; }
#word { font-size: 2em; margin: 1em 0; }
#scoreInput { font-size: 1.2em; width: 100px; }
button { font-size: 1em; padding: 0.5em 1em; margin-top: 1em; }
#result { margin-top: 1em; color: green; }
</style>
</head>
<body>
<div id="container">
<h2>Cemantle Solver</h2>
<div id="setup">
<label>Mot de départ : <input id="firstWord" type="text" /></label>
<label>Score de départ : <input id="firstScore" type="number" min="0" max="1000" /></label>
<button id="startBtn">Démarrer</button>
<span id="loading" style="display:none; margin-left:1em; color:#0077cc;">Chargement…</span>
</div>
<div id="game" style="display:none;">
<div id="word"></div>
<label for="scoreInput">Score :</label>
<input type="number" id="scoreInput" min="0" max="1000" />
<button id="submitScoreBtn">Valider</button>
<button id="skipWordBtn" style="margin-left:1em;">Passer</button>
<div id="result"></div>
<div id="remaining"></div>
<h3>Historique</h3>
<ul id="history"></ul>
</div>
</div>
<script>
let currentWord = '';
let history = [];
let remaining = 0;
function updateHistory() {
const histElem = document.getElementById('history');
histElem.innerHTML = '';
history.forEach(item => {
const li = document.createElement('li');
li.textContent = `${item.word} : ${item.score} (restants: ${item.remaining})`;
histElem.appendChild(li);
});
}
function updateRemaining() {
document.getElementById('remaining').textContent = `Mots restants : ${remaining}`;
}
async function startGame() {
const firstWord = document.getElementById('firstWord').value;
const firstScore = document.getElementById('firstScore').value;
if (!firstWord || !firstScore) {
alert('Remplis le mot et le score de départ');
return;
}
document.getElementById('loading').style.display = '';
try {
const resp = await fetch('/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ first_word: firstWord, first_score: firstScore })
});
const data = await resp.json();
if (data.error) {
alert(data.error);
document.getElementById('loading').style.display = 'none';
return;
}
history = data.history || [];
remaining = data.count;
document.getElementById('setup').style.display = 'none';
document.getElementById('game').style.display = '';
await nextWord();
} catch (e) {
alert('Erreur lors du chargement : ' + e);
} finally {
document.getElementById('loading').style.display = 'none';
}
}
async function nextWord() {
const resp = await fetch('/next', { method: 'POST' });
const data = await resp.json();
if (data.error) {
document.getElementById('word').textContent = 'Fini !';
document.getElementById('submitScoreBtn').disabled = true;
document.getElementById('skipWordBtn').disabled = true;
return;
}
currentWord = data.guess;
remaining = data.remaining;
history = data.history || [];
document.getElementById('word').textContent = currentWord;
document.getElementById('scoreInput').value = '';
document.getElementById('result').textContent = '';
document.getElementById('scoreInput').focus();
updateHistory();
updateRemaining();
}
document.getElementById('startBtn').onclick = startGame;
document.getElementById('submitScoreBtn').onclick = async function() {
const score = document.getElementById('scoreInput').value;
if (score === '') {
document.getElementById('result').textContent = 'Veuillez entrer un score.';
return;
}
const resp = await fetch('/score', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ score })
});
const data = await resp.json();
if (data.error) {
document.getElementById('result').textContent = data.error;
return;
}
if (data.result) {
document.getElementById('result').textContent = data.result;
}
history = data.history || history;
remaining = data.remaining || remaining;
updateHistory();
updateRemaining();
await nextWord();
};
document.getElementById('skipWordBtn').onclick = async function() {
const resp = await fetch('/skip', { method: 'POST' });
const data = await resp.json();
if (data.error) {
document.getElementById('result').textContent = data.error;
return;
}
if (data.result) {
document.getElementById('result').textContent = data.result;
}
history = data.history || history;
remaining = data.remaining || remaining;
updateHistory();
updateRemaining();
await nextWord();
};
</script>
</body>
</html>