Ajoute un script de démo pour tester dialogues/surbrillance/message différé

Les trois mécanismes de guidage du joueur discutés (boîte de dialogue en
popup, réaction à un clic, mise en évidence d'un élément à cliquer)
existent DÉJÀ dans le moteur (voir README, section "Survol, séquences
temporisées, surbrillance, overlay, verrouillage") : widget "Superposition
/ boîte de dialogue", action "Modifier un élément → Surbrillance", et
déclencheur "affichage" combiné à l'action "Attendre" pour un message qui
arrive tout seul après quelques secondes. Rien à coder côté moteur.

scripts/build_demo_dialogues.py construit, via les VRAIES routes Flask
(mêmes routes qu'utilise le navigateur), un jeu de démo persistant
("Demo Dialogues Surbrillance") avec deux écrans :
- "Tutoriel" : dialogue de bienvenue à l'ouverture de l'écran -> son
  bouton OK ferme le dialogue et met un autre bouton en surbrillance ->
  cliquer ce bouton l'éteint, le désactive (verrouillage anti-reclic) et
  ouvre un dialogue "Bravo" en réaction, fermable à son tour.
- "Message différé" : un dialogue "mentor" apparaît tout seul 3 secondes
  après l'affichage de l'écran (affichage -> attendre -> visibilité),
  sans aucune action du joueur.

Vérifié via /runtime-payload (nombre de nœuds/arêtes attendu sur les deux
écrans) et /play (overlay + surbrillance + attente bien exposés côté
rendu). 128 tests toujours au vert (script indépendant, aucun changement
moteur).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-27 17:59:21 +02:00
co-authored by Claude Sonnet 5
parent 169b720620
commit 28d8cd8cd0
+177
View File
@@ -0,0 +1,177 @@
"""Crée un jeu de démo "Démo — Dialogues & Surbrillance" qui exerce, bout
en bout, les trois mécanismes de guidage du joueur déjà présents dans le
moteur (voir README, section "Survol, séquences temporisées, surbrillance,
overlay, verrouillage") :
- Écran 1 "Tutoriel" : une boîte de dialogue (widget "superposition")
s'ouvre TOUTE SEULE à l'affichage de l'écran (déclencheur "affichage"),
accueille le joueur et lui indique quoi faire. Son bouton "OK" la
referme ET met un autre bouton en surbrillance (liseré clignotant) pour
guider le clic suivant. Cliquer ce bouton met fin à la surbrillance, le
désactive (grisé, ne peut plus être recliqué) et ouvre une DEUXIÈME
boîte de dialogue "Bravo" en réaction au clic — fermable à son tour.
- Écran 2 "Message différé" : une boîte de dialogue "arrive toute seule"
3 secondes après l'affichage de l'écran (déclencheur "affichage" ->
action "attendre" -> action "Modifier un élément → Visibilité"), sans
aucune action du joueur — utile pour un message de mentor/notification.
Ce script utilise le VRAI client de test Flask contre les VRAIES routes de
l'appli (mêmes routes que celles appelées par le navigateur), et crée un
jeu PERSISTANT sous projects/ (contrairement aux tests pytest, il n'est
PAS supprimé à la fin) — pensé pour être ouvert et joué dans le navigateur
après coup.
Usage : python3 scripts/build_demo_dialogues.py
"""
import json
import re
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.flask_app import app
from core import jinja_filters # noqa: F401 - enregistre les filtres Jinja
import routes # noqa: F401 - enregistre toutes les routes
def _redirect_id(resp, pattern):
return int(re.search(pattern, resp.headers["Location"]).group(1))
def _create_screen(client, slug, name):
resp = client.post(f"/game/{slug}/screens/new", data={"name": name}, follow_redirects=False)
return _redirect_id(resp, r"/screens/(\d+)/edit")
def _add_element(client, slug, screen_id, widget):
resp = client.post(f"/game/{slug}/screens/{screen_id}/elements/add", data={"widget": widget}, follow_redirects=False)
return _redirect_id(resp, r"selected=(\d+)")
def _add_child(client, slug, parent_id, widget):
resp = client.post(f"/game/{slug}/elements/{parent_id}/children/add", data={"widget": widget}, follow_redirects=False)
return _redirect_id(resp, r"selected=(\d+)")
def _save(client, slug, element_id, **ctrl):
data = {f"ctrl_{k}": v for k, v in ctrl.items()}
client.post(f"/game/{slug}/elements/{element_id}/save", data=data)
def _add_node(client, slug, screen_id, **fields):
resp = client.post(
f"/game/{slug}/screens/{screen_id}/flow/nodes/add",
data=json.dumps({**fields}),
content_type="application/json",
)
assert resp.status_code == 200, resp.get_data(as_text=True)
return resp.get_json()["id"]
def _add_edge(client, slug, screen_id, from_id, to_id, from_port="out"):
resp = client.post(
f"/game/{slug}/screens/{screen_id}/flow/edges/add",
data=json.dumps({"from_node_id": from_id, "from_port": from_port, "to_node_id": to_id}),
content_type="application/json",
)
assert resp.status_code == 200, resp.get_data(as_text=True)
def _modifier_element(client, slug, screen_id, target_id, prop, value):
return _add_node(
client, slug, screen_id,
node_type="action", action_type="modifier_element",
target_element_id=target_id, element_property=prop, element_value=value,
)
def build():
app.config["TESTING"] = True
with app.test_client() as client:
resp = client.post("/games/new", data={"name": "Demo Dialogues Surbrillance"}, follow_redirects=False)
assert resp.status_code == 302, resp.get_data(as_text=True)
slug = resp.headers["Location"].rstrip("/").split("/")[-1]
print(f"Jeu créé : slug={slug}")
# ---------------- Écran 1 : Tutoriel ----------------
screen1 = _create_screen(client, slug, "Tutoriel")
overlay_bienvenue = _add_element(client, slug, screen1, "superposition")
_save(client, slug, overlay_bienvenue, couleur_boite="#20263a", arrondi="16")
t1 = _add_child(client, slug, overlay_bienvenue, "titre")
_save(client, slug, t1, content="Bienvenue !")
t2 = _add_child(client, slug, overlay_bienvenue, "texte")
_save(client, slug, t2, content="Ce petit tutoriel te montre comment fonctionnent les dialogues et la surbrillance. Clique sur OK pour commencer.")
btn_ok = _add_child(client, slug, overlay_bienvenue, "bouton")
_save(client, slug, btn_ok, content="OK, c'est parti")
btn_target = _add_element(client, slug, screen1, "bouton")
_save(client, slug, btn_target, content="Clique-moi !")
overlay_bravo = _add_element(client, slug, screen1, "superposition")
_save(client, slug, overlay_bravo, couleur_boite="#1f3a24", arrondi="16")
t3 = _add_child(client, slug, overlay_bravo, "titre")
_save(client, slug, t3, content="Bravo !")
t4 = _add_child(client, slug, overlay_bravo, "texte")
_save(client, slug, t4, content="Tu as cliqué le bon bouton. Le tutoriel est terminé.")
btn_close_bravo = _add_child(client, slug, overlay_bravo, "bouton")
_save(client, slug, btn_close_bravo, content="Fermer")
# Déclencheur "affichage" (écran entier) -> ouvre le dialogue de bienvenue.
trig_affichage = _add_node(client, slug, screen1, node_type="trigger", trigger_event="affichage")
act_open_bienvenue = _modifier_element(client, slug, screen1, overlay_bienvenue, "visibilite", "visible")
_add_edge(client, slug, screen1, trig_affichage, act_open_bienvenue)
# Clic sur "OK" -> ferme le dialogue de bienvenue ET met le bouton cible en surbrillance.
trig_ok = _add_node(client, slug, screen1, node_type="trigger", trigger_element_id=btn_ok, trigger_event="clic")
act_close_bienvenue = _modifier_element(client, slug, screen1, overlay_bienvenue, "visibilite", "masque")
act_highlight_on = _modifier_element(client, slug, screen1, btn_target, "surbrillance", "on")
_add_edge(client, slug, screen1, trig_ok, act_close_bienvenue)
_add_edge(client, slug, screen1, trig_ok, act_highlight_on)
# Clic sur le bouton cible -> arrête la surbrillance, le désactive, ouvre "Bravo".
trig_target = _add_node(client, slug, screen1, node_type="trigger", trigger_element_id=btn_target, trigger_event="clic")
act_highlight_off = _modifier_element(client, slug, screen1, btn_target, "surbrillance", "off")
act_lock_target = _modifier_element(client, slug, screen1, btn_target, "desactive", "on")
act_open_bravo = _modifier_element(client, slug, screen1, overlay_bravo, "visibilite", "visible")
_add_edge(client, slug, screen1, trig_target, act_highlight_off)
_add_edge(client, slug, screen1, trig_target, act_lock_target)
_add_edge(client, slug, screen1, trig_target, act_open_bravo)
# Clic sur "Fermer" (dans "Bravo") -> referme "Bravo".
trig_close_bravo = _add_node(client, slug, screen1, node_type="trigger", trigger_element_id=btn_close_bravo, trigger_event="clic")
act_close_bravo = _modifier_element(client, slug, screen1, overlay_bravo, "visibilite", "masque")
_add_edge(client, slug, screen1, trig_close_bravo, act_close_bravo)
# ---------------- Écran 2 : Message différé ----------------
screen2 = _create_screen(client, slug, "Message différé")
overlay_mentor = _add_element(client, slug, screen2, "superposition")
_save(client, slug, overlay_mentor, couleur_boite="#2a2440", arrondi="16")
t5 = _add_child(client, slug, overlay_mentor, "titre")
_save(client, slug, t5, content="Message du mentor")
t6 = _add_child(client, slug, overlay_mentor, "texte")
_save(client, slug, t6, content="Ce message est apparu tout seul, 3 secondes après l'arrivée sur cet écran — sans aucun clic.")
btn_close_mentor = _add_child(client, slug, overlay_mentor, "bouton")
_save(client, slug, btn_close_mentor, content="Fermer")
info2 = _add_element(client, slug, screen2, "texte")
_save(client, slug, info2, content="Attends 3 secondes sans rien faire : un message va apparaître tout seul.")
trig_affichage2 = _add_node(client, slug, screen2, node_type="trigger", trigger_event="affichage")
act_wait = _add_node(client, slug, screen2, node_type="action", action_type="attendre", data_value="3")
act_open_mentor = _modifier_element(client, slug, screen2, overlay_mentor, "visibilite", "visible")
_add_edge(client, slug, screen2, trig_affichage2, act_wait)
_add_edge(client, slug, screen2, act_wait, act_open_mentor)
trig_close_mentor = _add_node(client, slug, screen2, node_type="trigger", trigger_element_id=btn_close_mentor, trigger_event="clic")
act_close_mentor = _modifier_element(client, slug, screen2, overlay_mentor, "visibilite", "masque")
_add_edge(client, slug, screen2, trig_close_mentor, act_close_mentor)
print(f"Terminé. Ouvre http://127.0.0.1:5050/game/{slug}/play pour tester.")
print(f"Éditeur : http://127.0.0.1:5050/game/{slug}/screens/{screen1}/edit")
if __name__ == "__main__":
build()