From 28d8cd8cd0037b0323af1a47a90168a42d40e8dd Mon Sep 17 00:00:00 2001 From: william Date: Thu, 27 Aug 2026 17:59:21 +0200 Subject: [PATCH] =?UTF-8?q?Ajoute=20un=20script=20de=20d=C3=A9mo=20pour=20?= =?UTF-8?q?tester=20dialogues/surbrillance/message=20diff=C3=A9r=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- scripts/build_demo_dialogues.py | 177 ++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 scripts/build_demo_dialogues.py diff --git a/scripts/build_demo_dialogues.py b/scripts/build_demo_dialogues.py new file mode 100644 index 00000000..64d4e7dd --- /dev/null +++ b/scripts/build_demo_dialogues.py @@ -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()