Score/Progression (Phase 1.1 de la feuille de route produit)
Concept de premier ordre, distinct du système de variables globales — préalable identifié aux futurs exports SCORM/xAPI (note de cadrage .claude/Forge_Engine_Cadrage.pdf) : ces standards ont besoin d'un signal "score"/"terminé" propre, pas d'une convention sur une variable choisie par le créateur. - db/scoring/ (même patron que db/global_vars/) : table _scoring, un score numérique + un statut (non_commence/en_cours/termine/reussi/ echoue) par joueur (voir db.PLAYER_SHARED pour l'aperçu créateur). - Deux nouvelles actions de flux, dans les deux éditeurs (document et scène) : "Modifier le score" (réutilise le vocabulaire d'opérations déjà là pour "Modifier une variable" — incrémenter/définir/etc., aucune nouvelle colonne de nœud) et "Définir le statut de la partie". - Routes créateur (routes/flow/flow_node_run_score.py, flow_node_run_status.py) + miroirs publics par joueur (routes/public_play/) — même principe que flow_node_run_variable.py. - GET /game/<slug>/scoring/<player_id> : lecture interne, pas exposée au joueur, préparée pour être consommée par le futur export Web/SCORM. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
76a50fa87a
commit
9776fbc057
@@ -0,0 +1,6 @@
|
||||
"""Score/progression (voir le plan "Feuille de route produit") : un
|
||||
concept de premier ordre, distinct du système de variables globales
|
||||
(db/global_vars/) — un score numérique et un statut de partie, TOUJOURS
|
||||
par joueur (voir db.PLAYER_SHARED pour l'aperçu créateur), préalable aux
|
||||
exports SCORM/xAPI (qui ont besoin d'un signal "score"/"terminé" propre,
|
||||
pas d'une convention sur une variable choisie par le créateur)."""
|
||||
@@ -0,0 +1,22 @@
|
||||
from ..connection import connect
|
||||
|
||||
|
||||
def ensure_scoring_schema(slug):
|
||||
"""Migration légère (même principe que ensure_global_vars_schema.py) :
|
||||
crée _scoring si absente. Une seule ligne par joueur (player_id, voir
|
||||
db.PLAYER_SHARED pour l'aperçu créateur — même modèle que
|
||||
db/global_vars/, mais pas de notion "per_player=0" ici : le score
|
||||
d'UN joueur n'a de sens que pour CE joueur, jamais partagé)."""
|
||||
conn = connect(slug)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS _scoring (
|
||||
player_id TEXT PRIMARY KEY,
|
||||
score REAL NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'non_commence',
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -0,0 +1,17 @@
|
||||
from ..connection import connect
|
||||
from ..global_vars.ensure_global_vars_schema import PLAYER_SHARED
|
||||
from .ensure_scoring_schema import ensure_scoring_schema
|
||||
|
||||
|
||||
def get_score(slug, player_id=PLAYER_SHARED):
|
||||
"""Score/statut de CE joueur — (0, "non_commence") tant qu'il n'a
|
||||
encore rien écrit (voir set_score_value.py/set_status.py, qui créent
|
||||
la ligne à la première écriture, même patron que
|
||||
update_global_variable_value.py)."""
|
||||
ensure_scoring_schema(slug)
|
||||
conn = connect(slug)
|
||||
row = conn.execute("SELECT * FROM _scoring WHERE player_id = ?", (player_id,)).fetchone()
|
||||
conn.close()
|
||||
if not row:
|
||||
return {"player_id": player_id, "score": 0, "status": "non_commence"}
|
||||
return dict(row)
|
||||
@@ -0,0 +1,23 @@
|
||||
from ..connection import connect
|
||||
from ..global_vars.ensure_global_vars_schema import PLAYER_SHARED
|
||||
from .ensure_scoring_schema import ensure_scoring_schema
|
||||
|
||||
|
||||
def set_score_value(slug, new_value, player_id=PLAYER_SHARED):
|
||||
"""Écrit une nouvelle valeur BRUTE (déjà calculée par l'appelant, voir
|
||||
screens/data_actions/apply_score_action.py) — même patron que
|
||||
update_global_variable_value.py : la ligne de ce joueur est créée à sa
|
||||
toute première écriture (INSERT ... ON CONFLICT évite un aller-retour
|
||||
SELECT préalable, un joueur ne peut écrire que la SIENNE)."""
|
||||
ensure_scoring_schema(slug)
|
||||
conn = connect(slug)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO _scoring (player_id, score) VALUES (?, ?)
|
||||
ON CONFLICT(player_id) DO UPDATE SET score = excluded.score, updated_at = CURRENT_TIMESTAMP
|
||||
""",
|
||||
(player_id, new_value),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
@@ -0,0 +1,25 @@
|
||||
from ..connection import connect
|
||||
from ..constants import SCORE_STATUS_LABELS
|
||||
from ..global_vars.ensure_global_vars_schema import PLAYER_SHARED
|
||||
from .ensure_scoring_schema import ensure_scoring_schema
|
||||
|
||||
|
||||
def set_status(slug, status, player_id=PLAYER_SHARED):
|
||||
"""Voir set_score_value.py pour le patron (upsert, ligne créée à la
|
||||
première écriture). `status` doit être une clé de
|
||||
db.SCORE_STATUS_LABELS — un statut inconnu (ex. faute de frappe dans
|
||||
une valeur de nœud de flux) est refusé plutôt qu'écrit tel quel."""
|
||||
if status not in SCORE_STATUS_LABELS:
|
||||
return False
|
||||
ensure_scoring_schema(slug)
|
||||
conn = connect(slug)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO _scoring (player_id, status) VALUES (?, ?)
|
||||
ON CONFLICT(player_id) DO UPDATE SET status = excluded.status, updated_at = CURRENT_TIMESTAMP
|
||||
""",
|
||||
(player_id, status),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
Reference in New Issue
Block a user