Add make help, Enter key validation, Android/macOS/web/ChromeOS exports (0.9.0)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-12 12:49:25 +02:00
parent bf180eea01
commit 87919826ef
14 changed files with 268 additions and 27 deletions
+3 -1
View File
@@ -10,6 +10,7 @@ Jeu éducatif de français CE1 à CM2 (7–11 ans), aventure 2D, hors ligne, God
Le `Makefile` est le seul point d'entrée pour les opérations système. Tenir cette liste à jour. Le `Makefile` est le seul point d'entrée pour les opérations système. Tenir cette liste à jour.
- `make help` : liste les commandes (toute cible du Makefile suivie de `## description` y figure : tenir ces commentaires à jour)
- `make setup` : installe l'outillage (lint, tests, modèles d'export) - `make setup` : installe l'outillage (lint, tests, modèles d'export)
- `make import` : importe les ressources dans `.godot/` (deux passes ; appelé automatiquement par `run`, `test`, `validate-data`, `export-*`, `screenshot` : indispensable sur un dépôt fraîchement cloné) - `make import` : importe les ressources dans `.godot/` (deux passes ; appelé automatiquement par `run`, `test`, `validate-data`, `export-*`, `screenshot` : indispensable sur un dépôt fraîchement cloné)
- `make run` : lance le jeu - `make run` : lance le jeu
@@ -22,7 +23,8 @@ Le `Makefile` est le seul point d'entrée pour les opérations système. Tenir c
- `make generate-sounds` : génère les effets sonores et musiques d'ambiance (synthèse procédurale numpy + `ffmpeg`) dans `assets/audio/sfx/` et `assets/audio/music/`, sans écraser l'existant (`--force` pour régénérer) - `make generate-sounds` : génère les effets sonores et musiques d'ambiance (synthèse procédurale numpy + `ffmpeg`) dans `assets/audio/sfx/` et `assets/audio/music/`, sans écraser l'existant (`--force` pour régénérer)
- `make export-linux` / `make export-windows` : exports dans `build/` - `make export-linux` / `make export-windows` : exports dans `build/`
- `make screenshot SCENE=… OUT=… ARGS=…` : capture le rendu réel d'une scène dans une fenêtre (outil de développement, ouvre brièvement une fenêtre) - `make screenshot SCENE=… OUT=… ARGS=…` : capture le rendu réel d'une scène dans une fenêtre (outil de développement, ouvre brièvement une fenêtre)
- `make package` : réexporte puis produit les archives distribuables (exécutable unique + licence + crédits + notice) et `SHA256SUMS` dans `dist/` - `make export-android` / `make export-macos` / `make export-web` : APK Android (arm64 + x86_64, aussi pour ChromeOS), application macOS universelle (zip, signature ad hoc), version web
- `make package` : réexporte puis produit les archives distribuables (Linux, Windows, Android, macOS, ChromeOS ; licence + crédits + notice) et `SHA256SUMS` dans `dist/`
## Conventions de code ## Conventions de code
+45 -16
View File
@@ -6,9 +6,17 @@ PYRIGHT := $(VENV)/bin/pyright
PYTHON := $(VENV)/bin/python3 PYTHON := $(VENV)/bin/python3
GD_SOURCES := autoload scripts tests GD_SOURCES := autoload scripts tests
.PHONY: setup import run lint format validate-data test check content-review generate-dictation-audio generate-sounds export-linux export-windows package screenshot .DEFAULT_GOAL := help
setup: .PHONY: help setup import run lint format validate-data test check content-review generate-dictation-audio generate-sounds export-linux export-windows export-android export-macos export-web package screenshot
# Liste les commandes : toute cible suivie de « ## description » y figure.
help: ## Affiche cette aide
@echo "Les Aventuriers des mots - commandes disponibles :"
@grep -E '^[a-zA-Z0-9_-]+:.*## ' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*## "} {printf " make %-26s %s\n", $$1, $$2}'
setup: ## Installe l'outillage (Godot, modeles d'export, lint, tests)
tools/setup.sh tools/setup.sh
# Importe les ressources (polices, SVG, audio...) dans .godot/. Indispensable # Importe les ressources (polices, SVG, audio...) dans .godot/. Indispensable
@@ -16,49 +24,70 @@ setup:
# jeu sans theme. Deux passes : le theme n'est valide qu'une fois les polices # jeu sans theme. Deux passes : le theme n'est valide qu'une fois les polices
# importees. La premiere passe peut echouer (theme charge avant les polices) : # importees. La premiere passe peut echouer (theme charge avant les polices) :
# son code de retour est ignore, la seconde fait foi. # son code de retour est ignore, la seconde fait foi.
import: import: ## Importe les ressources dans .godot/ (deux passes)
-@$(GODOT_BIN) --headless --path . --import >/dev/null 2>&1 -@$(GODOT_BIN) --headless --path . --import >/dev/null 2>&1
$(GODOT_BIN) --headless --path . --import >/dev/null $(GODOT_BIN) --headless --path . --import >/dev/null
run: import run: import ## Lance le jeu
$(GODOT_BIN) --path . $(GODOT_BIN) --path .
lint: lint: ## Analyse statique (gdlint, pyright)
$(GDLINT) $(GD_SOURCES) $(GDLINT) $(GD_SOURCES)
$(PYRIGHT) tools/ $(PYRIGHT) tools/
format: format: ## Formate le GDScript
$(GDFORMAT) $(GD_SOURCES) $(GDFORMAT) $(GD_SOURCES)
validate-data: import validate-data: import ## Valide les JSON de data/ contre leurs schemas
$(GODOT_BIN) --headless --path . -s res://scripts/data/validate_data_cli.gd $(GODOT_BIN) --headless --path . -s res://scripts/data/validate_data_cli.gd
test: import test: import ## Lance les tests en mode headless
$(GODOT_BIN) --headless --path . -s res://addons/gut/gut_cmdln.gd -gdir=res://tests -ginclude_subdirs -gexit $(GODOT_BIN) --headless --path . -s res://addons/gut/gut_cmdln.gd -gdir=res://tests -ginclude_subdirs -gexit
check: lint validate-data test check: lint validate-data test ## lint + validate-data + test (avant chaque commit)
content-review: content-review: ## Regenere docs/review/content-review.md
$(PYTHON) tools/generate_content_review.py $(PYTHON) tools/generate_content_review.py
generate-dictation-audio: generate-dictation-audio: ## Genere l'audio provisoire des dictees (Piper)
$(PYTHON) tools/generate_dictation_audio.py $(PYTHON) tools/generate_dictation_audio.py
generate-sounds: generate-sounds: ## Genere les effets sonores et musiques
$(PYTHON) tools/generate_sounds.py $(PYTHON) tools/generate_sounds.py
export-linux: import export-linux: import ## Exporte la version Linux dans build/
mkdir -p build/linux mkdir -p build/linux
$(GODOT_BIN) --headless --path . --export-release "Linux" build/linux/aventuriers_des_mots.x86_64 $(GODOT_BIN) --headless --path . --export-release "Linux" build/linux/aventuriers_des_mots.x86_64
export-windows: import export-windows: import ## Exporte la version Windows dans build/
mkdir -p build/windows mkdir -p build/windows
$(GODOT_BIN) --headless --path . --export-release "Windows" build/windows/aventuriers_des_mots.exe $(GODOT_BIN) --headless --path . --export-release "Windows" build/windows/aventuriers_des_mots.exe
package: package: ## Reexporte tout et produit les archives dans dist/
tools/package.sh tools/package.sh
# Capture d'une scene dans une vraie fenetre (voir tools/screenshot.gd) : # Capture d'une scene dans une vraie fenetre (voir tools/screenshot.gd) :
# make screenshot SCENE=res://scenes/world/world_map.tscn OUT=/tmp/map.png ARGS="CE2" # make screenshot SCENE=res://scenes/world/world_map.tscn OUT=/tmp/map.png ARGS="CE2"
screenshot: import screenshot: import ## Capture une scene (SCENE=... OUT=... ARGS=...)
$(GODOT_BIN) --path . -s res://tools/screenshot.gd -- $(SCENE) $(OUT) $(ARGS) $(GODOT_BIN) --path . -s res://tools/screenshot.gd -- $(SCENE) $(OUT) $(ARGS)
# Android : APK unique (arm64 + x86_64, donc aussi les Chromebooks). Signature
# de test avec la cle de debogage de Godot (standard, publique) : aucune cle
# privee dans le depot. Pour publier sur un magasin, fournir sa propre cle via
# GODOT_ANDROID_KEYSTORE_RELEASE_PATH / _USER / _PASSWORD.
ANDROID_DEBUG_KEYSTORE ?= $(HOME)/.local/share/godot/keystores/debug.keystore
export-android: import ## Exporte l'APK Android / ChromeOS dans build/android/
mkdir -p build/android
GODOT_ANDROID_KEYSTORE_RELEASE_PATH="$${GODOT_ANDROID_KEYSTORE_RELEASE_PATH:-$(ANDROID_DEBUG_KEYSTORE)}" \
GODOT_ANDROID_KEYSTORE_RELEASE_USER="$${GODOT_ANDROID_KEYSTORE_RELEASE_USER:-androiddebugkey}" \
GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD="$${GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD:-android}" \
$(GODOT_BIN) --headless --path . --export-release "Android" build/android/aventuriers_des_mots.apk
export-macos: import ## Exporte l'application macOS (zip) dans build/macos/
mkdir -p build/macos
$(GODOT_BIN) --headless --path . --export-release "macOS" build/macos/aventuriers_des_mots.zip
export-web: import ## Exporte la version Web (navigateur, ChromeOS) dans build/web/
mkdir -p build/web
$(GODOT_BIN) --headless --path . --export-release "Web" build/web/index.html
+3 -2
View File
@@ -2,7 +2,7 @@
An educational French-language game for 7-9 year olds (French grades CE1-CE2): an offline 2D adventure with an explorable map, a guide character, and grammar, spelling, and dictation exercises. An educational French-language game for 7-9 year olds (French grades CE1-CE2): an offline 2D adventure with an explorable map, a guide character, and grammar, spelling, and dictation exercises.
Open source project, in development. Current status: version 0.8.0, playable offline on Linux and Windows — profiles, saves (with export and restore in the adult area), stars and an item collection, sound effects and music, settings (volumes, text size, animations), a progress dashboard for adults, three exercise types (multiple choice, sentence correction, dictation), CE1, CE2, CM1 and CM2 in three boards each (homophones, verbs, word spelling), 111 missions. All pedagogical content is still `draft` (awaiting validation) and dictation voices are placeholders (see `assets/CREDITS.md`; see `docs/specs/decisions.md` for scoping decisions and `docs/specs/cahier-des-charges.md` for the full specification, both in French — this is a French-curriculum product). Open source project, in development. Current status: version 0.9.0, playable offline on Linux, Windows, macOS, Android and ChromeOS (macOS, Android and web builds untested on real hardware) — profiles, saves (with export and restore in the adult area), stars and an item collection, sound effects and music, settings (volumes, text size, animations), a progress dashboard for adults, three exercise types (multiple choice, sentence correction, dictation), CE1, CE2, CM1 and CM2 in three boards each (homophones, verbs, word spelling), 111 missions. All pedagogical content is still `draft` (awaiting validation) and dictation voices are placeholders (see `assets/CREDITS.md`; see `docs/specs/decisions.md` for scoping decisions and `docs/specs/cahier-des-charges.md` for the full specification, both in French — this is a French-curriculum product).
## Tech stack ## Tech stack
@@ -29,7 +29,8 @@ make run # runs the game
| `make check` | lint + validate-data + test, must pass before every commit | | `make check` | lint + validate-data + test, must pass before every commit |
| `make content-review` | regenerates `docs/review/content-review.md` (content validation document) | | `make content-review` | regenerates `docs/review/content-review.md` (content validation document) |
| `make generate-dictation-audio` | generates the placeholder dictation audio | | `make generate-dictation-audio` | generates the placeholder dictation audio |
| `make export-linux` / `make export-windows` | exports into `build/` | | `make help` | lists the available commands |
| `make export-linux` / `make export-windows` / `make export-android` / `make export-macos` / `make export-web` | exports into `build/` |
| `make package` | builds the distributable archives in `dist/` (single executable + license + credits) | | `make package` | builds the distributable archives in `dist/` (single executable + license + credits) |
## Structure ## Structure
+3 -2
View File
@@ -2,7 +2,7 @@
Jeu éducatif de français pour les CE1-CE2 (7-9 ans) : une aventure 2D hors ligne, avec une carte à explorer, un personnage guide, et des exercices de grammaire, d'orthographe et de dictées. Jeu éducatif de français pour les CE1-CE2 (7-9 ans) : une aventure 2D hors ligne, avec une carte à explorer, un personnage guide, et des exercices de grammaire, d'orthographe et de dictées.
Projet open source, en développement. Statut actuel : version 0.8.0, jouable hors ligne sur Linux et Windows — profils, sauvegardes (avec export et restauration dans l'espace adulte), étoiles et collection d'objets, sons et musique, réglages (volumes, taille du texte, animations), tableau de progrès pour l'adulte, trois types d'exercices (choix multiple, correction de phrase, dictée), CE1, CE2, CM1 et CM2 en trois tableaux chacun (homophones, verbes, orthographe des mots), 111 missions. Contenu pédagogique entièrement en brouillon (`draft`, à valider) et voix des dictées provisoires (voir `assets/CREDITS.md` ; voir `docs/specs/decisions.md` pour les décisions de cadrage et `docs/specs/cahier-des-charges.md` pour la spécification complète). Projet open source, en développement. Statut actuel : version 0.9.0, jouable hors ligne sur Linux, Windows, macOS, Android et ChromeOS (versions macOS, Android et web non testées sur matériel) — profils, sauvegardes (avec export et restauration dans l'espace adulte), étoiles et collection d'objets, sons et musique, réglages (volumes, taille du texte, animations), tableau de progrès pour l'adulte, trois types d'exercices (choix multiple, correction de phrase, dictée), CE1, CE2, CM1 et CM2 en trois tableaux chacun (homophones, verbes, orthographe des mots), 111 missions. Contenu pédagogique entièrement en brouillon (`draft`, à valider) et voix des dictées provisoires (voir `assets/CREDITS.md` ; voir `docs/specs/decisions.md` pour les décisions de cadrage et `docs/specs/cahier-des-charges.md` pour la spécification complète).
## Pile technique ## Pile technique
@@ -29,7 +29,8 @@ make run # lance le jeu
| `make check` | lint + validate-data + test, à passer avant chaque commit | | `make check` | lint + validate-data + test, à passer avant chaque commit |
| `make content-review` | régénère `docs/review/content-review.md` (document de validation du contenu) | | `make content-review` | régénère `docs/review/content-review.md` (document de validation du contenu) |
| `make generate-dictation-audio` | génère l'audio provisoire des dictées | | `make generate-dictation-audio` | génère l'audio provisoire des dictées |
| `make export-linux` / `make export-windows` | exports dans `build/` | | `make help` | liste les commandes disponibles |
| `make export-linux` / `make export-windows` / `make export-android` / `make export-macos` / `make export-web` | exports dans `build/` |
| `make package` | produit les archives distribuables dans `dist/` (exécutable unique + licence + crédits) | | `make package` | produit les archives distribuables dans `dist/` (exécutable unique + licence + crédits) |
## Structure ## Structure
+9
View File
@@ -425,3 +425,12 @@ Volume : 6 tableaux, 18 règles, 144 QCM, 72 corrections, 54 dictées (voix Pipe
- **Indice qui donnait la réponse** (« avoir » avec « je ») reformulé. - **Indice qui donnait la réponse** (« avoir » avec « je ») reformulé.
Les corrections et les dictées CE1/CE2 n'ont pas révélé d'autre problème ; les tests de doublons, quasi-doublons et de position de la bonne réponse sont verts. **Non vérifié** : relecture par un enseignant. Les corrections et les dictées CE1/CE2 n'ont pas révélé d'autre problème ; les tests de doublons, quasi-doublons et de position de la bonne réponse sont verts. **Non vérifié** : relecture par un enseignant.
**Plateformes et touche Entrée (0.9.0)**
- **Entrée valide** : dans la dictée et dans les champs de correction, la touche Entrée équivaut au bouton « Valider ». L'hôte d'exercice ignore toute validation tant que le panneau de retour est ouvert (une seule réponse comptée par essai).
- **Nouvelles cibles** : `make export-android` (APK arm64 + x86_64), `make export-macos` (application universelle, zip, signature ad hoc, non notariée), `make export-web` (Compatibility, sans threads). `make package` produit cinq archives.
- **ChromeOS** : pas de cible propre au moteur ; l'archive regroupe l'APK (runtime Android) et la version web à servir en local. Le binaire Linux couvre aussi Crostini.
- **Signature Android** : clé de débogage publique de Godot, via variables d'environnement (aucun secret dans le dépôt). Une publication sur un magasin demandera une vraie clé (`GODOT_ANDROID_KEYSTORE_RELEASE_*`).
- **Réglage de projet** : import ETC2/ASTC activé (exigé par les exports mobile et macOS universel).
- **Non vérifié** : aucune des trois nouvelles versions n'a été lancée sur un appareil. Le tactile (clavier virtuel Android pour les champs de saisie, taille des boutons), le stockage `user://` et l'audio sur Android/web restent à tester à la main.
+7
View File
@@ -75,3 +75,10 @@ Tout ce qui suit ne peut pas être vérifié sans écran (rendu, mise en page, a
- Export macOS, Android, iOS : hors périmètre de cette version (voir prompt de lancement, Objectif). - Export macOS, Android, iOS : hors périmètre de cette version (voir prompt de lancement, Objectif).
- Clavier virtuel tactile, formats d'écran mobile : hors périmètre de cette version. - Clavier virtuel tactile, formats d'écran mobile : hors périmètre de cette version.
- Animations : aucune n'existe encore (interface statique assumée pour ce prototype). - Animations : aucune n'existe encore (interface statique assumée pour ce prototype).
## Plateformes supplémentaires (0.9.0)
- `make export-android` : APK structurellement vérifié (paquet, ABI arm64-v8a et x86_64, signature) ; **jamais installé** : à tester sur une tablette, notamment le clavier virtuel dans la dictée et les champs de correction, la taille des boutons au doigt, la sauvegarde et le son.
- `make export-macos` : archive vérifiée (structure .app, signature ad hoc) ; **jamais lancée** (pas de Mac). Au premier lancement : clic droit > Ouvrir.
- `make export-web` : fichiers générés ; **jamais ouvert dans un navigateur**. Servir `build/web/` avec `python3 -m http.server`, ouvrir dans Chrome, vérifier son (le navigateur exige un premier clic), sauvegarde et plein écran.
- Touche Entrée : en dictée et en correction, Entrée valide ; une seconde pression sur Entrée pendant le retour ne doit pas compter une réponse de plus.
+94
View File
@@ -45,3 +45,97 @@ script_export_mode=2
binary_format/architecture="x86_64" binary_format/architecture="x86_64"
binary_format/embed_pck=true binary_format/embed_pck=true
[preset.2]
name="Android"
platform="Android"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="tests/*, addons/gut/*"
export_path="build/android/aventuriers_des_mots.apk"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.2.options]
gradle_build/use_gradle_build=false
package/unique_name="org.deunix.aventuriersdesmots"
package/name="Les Aventuriers des mots"
package/signed=true
version/code=900
version/name="0.9.0"
architectures/armeabi-v7a=false
architectures/arm64-v8a=true
architectures/x86=false
architectures/x86_64=true
screen/immersive_mode=true
screen/support_small=true
screen/support_normal=true
screen/support_large=true
screen/support_xlarge=true
permissions/internet=false
[preset.3]
name="macOS"
platform="macOS"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="tests/*, addons/gut/*"
export_path="build/macos/aventuriers_des_mots.zip"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.3.options]
binary_format/architecture="universal"
application/bundle_identifier="org.deunix.aventuriersdesmots"
application/short_version="0.9.0"
application/version="0.9.0"
application/min_macos_version_x86_64="10.12"
application/min_macos_version_arm64="11.0"
codesign/codesign=1
notarization/notarization=0
[preset.4]
name="Web"
platform="Web"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="tests/*, addons/gut/*"
export_path="build/web/index.html"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.4.options]
variant/thread_support=false
vram_texture_compression/for_desktop=true
vram_texture_compression/for_mobile=false
progressive_web_app/enabled=false
+5 -1
View File
@@ -11,7 +11,7 @@ config_version=5
[application] [application]
config/name="Les Aventuriers des mots" config/name="Les Aventuriers des mots"
config/version="0.8.3" config/version="0.9.0"
run/main_scene="res://scenes/boot/boot.tscn" run/main_scene="res://scenes/boot/boot.tscn"
config/features=PackedStringArray("4.6", "Forward Plus") config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
@@ -36,3 +36,7 @@ window/stretch/aspect="expand"
[gui] [gui]
theme/custom="res://assets/interface/theme.tres" theme/custom="res://assets/interface/theme.tres"
[rendering]
textures/vram_compression/import_etc2_astc=true
+2
View File
@@ -30,6 +30,8 @@ func _ready() -> void:
_replay_button.pressed.connect(_on_replay_pressed) _replay_button.pressed.connect(_on_replay_pressed)
_validate_button.pressed.connect(_on_validate_pressed) _validate_button.pressed.connect(_on_validate_pressed)
# La touche Entree valide comme le bouton.
_text_edit.text_submitted.connect(func(_text: String) -> void: _on_validate_pressed())
func load_exercise(dictation: Dictionary) -> void: func load_exercise(dictation: Dictionary) -> void:
+7
View File
@@ -24,6 +24,7 @@ var _attempt_number := 1
var _completed_count := 0 var _completed_count := 0
var _seen_exercise_ids: Array[String] = [] var _seen_exercise_ids: Array[String] = []
var _outcomes: Array[String] = [] var _outcomes: Array[String] = []
var _feedback_open := false
var _pending_outcome := "" var _pending_outcome := ""
@onready var _exercise_container: Control = %ExerciseContainer @onready var _exercise_container: Control = %ExerciseContainer
@@ -155,6 +156,10 @@ func _on_dictation_submitted(submitted_text: String) -> void:
func _handle_result(result: CorrectionResult) -> void: func _handle_result(result: CorrectionResult) -> void:
# La touche Entree peut re-valider une saisie encore focalisee derriere le
# panneau de retour : une seule reponse est comptee par essai.
if _feedback_open:
return
var support_level := AttemptFlow.SupportLevel.EXPLANATION var support_level := AttemptFlow.SupportLevel.EXPLANATION
var is_terminal := result.is_correct var is_terminal := result.is_correct
@@ -183,6 +188,7 @@ func _show_feedback(
result: CorrectionResult, support_level: AttemptFlow.SupportLevel, is_terminal: bool result: CorrectionResult, support_level: AttemptFlow.SupportLevel, is_terminal: bool
) -> void: ) -> void:
var feedback: Control = preload("res://scenes/ui/feedback_panel.tscn").instantiate() var feedback: Control = preload("res://scenes/ui/feedback_panel.tscn").instantiate()
_feedback_open = true
_overlay_container.add_child(feedback) _overlay_container.add_child(feedback)
feedback.show_result(result, support_level) feedback.show_result(result, support_level)
feedback.continue_pressed.connect(_on_feedback_continued.bind(feedback, is_terminal)) feedback.continue_pressed.connect(_on_feedback_continued.bind(feedback, is_terminal))
@@ -190,6 +196,7 @@ func _show_feedback(
func _on_feedback_continued(feedback: Control, is_terminal: bool) -> void: func _on_feedback_continued(feedback: Control, is_terminal: bool) -> void:
feedback.queue_free() feedback.queue_free()
_feedback_open = false
if not is_terminal: if not is_terminal:
_attempt_number += 1 _attempt_number += 1
+2
View File
@@ -45,6 +45,8 @@ func _on_token_toggled(pressed: bool, index: int) -> void:
if not _correction_edits.has(index): if not _correction_edits.has(index):
var edit := LineEdit.new() var edit := LineEdit.new()
edit.placeholder_text = UIText.get_text("sentence_correction.correction_placeholder") edit.placeholder_text = UIText.get_text("sentence_correction.correction_placeholder")
# La touche Entree valide comme le bouton.
edit.text_submitted.connect(func(_text: String) -> void: _on_validate_pressed())
_corrections_box.add_child(edit) _corrections_box.add_child(edit)
_correction_edits[index] = edit _correction_edits[index] = edit
elif _correction_edits.has(index): elif _correction_edits.has(index):
@@ -0,0 +1,34 @@
extends GutTest
## La touche Entree valide une saisie comme le bouton « Valider ».
func test_enter_in_the_correction_field_submits_the_answer() -> void:
var scene: Control = load("res://scenes/exercises/sentence_correction.tscn").instantiate()
add_child_autofree(scene)
await wait_process_frames(1)
var exercise: Dictionary = DataLoader.read_json(
"res://data/exercises/ce1/ce1_accord_adjectif_correction_01.json"
)
scene.load_exercise(exercise)
await wait_process_frames(1)
(scene.get_node("%TokensBox").get_child(1) as Button).toggled.emit(true)
await wait_process_frames(1)
var edit: LineEdit = scene.get_node("%CorrectionsBox").get_child(0)
edit.text = "petits"
watch_signals(scene)
edit.text_submitted.emit(edit.text)
assert_signal_emitted_with_parameters(scene, "answer_submitted", [[1], {1: "petits"}])
func test_enter_in_the_dictation_field_submits_the_text() -> void:
var scene: Control = load("res://scenes/exercises/dictation.tscn").instantiate()
add_child_autofree(scene)
await wait_process_frames(1)
var edit: LineEdit = scene.get_node("%TextEdit")
edit.text = "La fleur est belle."
watch_signals(scene)
edit.text_submitted.emit(edit.text)
assert_signal_emitted_with_parameters(scene, "answer_submitted", ["La fleur est belle."])
@@ -0,0 +1 @@
uid://bahf2o3ldfkgv
+53 -5
View File
@@ -9,13 +9,24 @@ cd "$(dirname "$0")/.."
VERSION="$(sed -n 's/^config\/version="\(.*\)"/\1/p' project.godot)" VERSION="$(sed -n 's/^config\/version="\(.*\)"/\1/p' project.godot)"
[ -n "${VERSION}" ] || { echo "config/version absent de project.godot" >&2; exit 1; } [ -n "${VERSION}" ] || { echo "config/version absent de project.godot" >&2; exit 1; }
# Les presets d'export portent leur propre numero de version : on les aligne
# sur project.godot (version/code = major*10000 + minor*100 + patch).
IFS=. read -r V_MAJOR V_MINOR V_PATCH <<<"${VERSION}"
VERSION_CODE=$((V_MAJOR * 10000 + V_MINOR * 100 + V_PATCH))
sed -i \
-e "s/^version\/name=.*/version\/name=\"${VERSION}\"/" \
-e "s/^version\/code=.*/version\/code=${VERSION_CODE}/" \
-e "s/^application\/short_version=.*/application\/short_version=\"${VERSION}\"/" \
-e "s/^application\/version=.*/application\/version=\"${VERSION}\"/" \
export_presets.cfg
rm -rf build dist rm -rf build dist
make export-linux export-windows >/dev/null make export-linux export-windows export-android export-macos export-web >/dev/null
write_notice() { write_notice() {
cat > "$1/LISEZMOI.txt" <<NOTICE cat > "$1/LISEZMOI.txt" <<NOTICE
Les Aventuriers des mots ${VERSION} Les Aventuriers des mots ${VERSION}
Jeu éducatif de français CE1-CE2, hors ligne. Jeu éducatif de français CE1 à CM2, hors ligne.
Lancer : $2 Lancer : $2
Aucune installation ni connexion Internet n'est nécessaire. Aucune installation ni connexion Internet n'est nécessaire.
@@ -52,7 +63,44 @@ stage "${WINDOWS}" build/windows/aventuriers_des_mots.exe \
"double-clic sur aventuriers_des_mots.exe" \ "double-clic sur aventuriers_des_mots.exe" \
"%APPDATA%\\Godot\\app_userdata\\Les Aventuriers des mots\\" "%APPDATA%\\Godot\\app_userdata\\Les Aventuriers des mots\\"
(cd dist && tar czf "${LINUX}.tar.gz" "${LINUX}" && zip -qr "${WINDOWS}.zip" "${WINDOWS}") ANDROID="aventuriers-des-mots-${VERSION}-android"
(cd dist && sha256sum "${LINUX}.tar.gz" "${WINDOWS}.zip" > SHA256SUMS) MACOS="aventuriers-des-mots-${VERSION}-macos-universal"
rm -rf "dist/${LINUX}" "dist/${WINDOWS}" CHROMEOS="aventuriers-des-mots-${VERSION}-chromeos"
stage "${ANDROID}" build/android/aventuriers_des_mots.apk \
"copier l'APK sur la tablette ou le téléphone, l'ouvrir et accepter l'installation depuis cette source (Android 7 ou plus ; arm64 et x86_64)" \
"stockage privé de l'application (Réglages Android > Applications)"
# macOS : application non signée par un développeur identifié (signature ad hoc).
stage "${MACOS}" build/macos/aventuriers_des_mots.zip \
"décompresser l'archive interne, puis clic droit > Ouvrir la première fois (application non notariée)" \
"~/Library/Application Support/Godot/app_userdata/Les Aventuriers des mots/"
unzip -q "dist/${MACOS}/aventuriers_des_mots.zip" -d "dist/${MACOS}"
rm "dist/${MACOS}/aventuriers_des_mots.zip"
# ChromeOS : l'APK (runtime Android) ou la version Web servie en local.
stage "${CHROMEOS}" build/android/aventuriers_des_mots.apk \
"soit installer l'APK (Chromebook avec Android activé), soit servir le dossier web/ (voir LISEZMOI-WEB.txt)" \
"stockage privé de l'application, ou du navigateur pour la version web"
mkdir -p "dist/${CHROMEOS}/web"
cp build/web/* "dist/${CHROMEOS}/web/"
cat > "dist/${CHROMEOS}/LISEZMOI-WEB.txt" <<WEB
Version web (navigateur, hors ligne une fois servie localement)
Un navigateur ne lit pas ces fichiers directement : ils doivent être servis
par un petit serveur HTTP. Depuis le dossier web/ :
python3 -m http.server 8080
puis ouvrir http://localhost:8080 dans Chrome.
La progression est conservée dans le stockage du navigateur : ne pas
effacer les données du site, et utiliser l'espace adulte pour exporter.
WEB
(cd dist && tar czf "${LINUX}.tar.gz" "${LINUX}" \
&& zip -qr "${WINDOWS}.zip" "${WINDOWS}" \
&& zip -qr "${ANDROID}.zip" "${ANDROID}" \
&& zip -qr "${MACOS}.zip" "${MACOS}" \
&& zip -qr "${CHROMEOS}.zip" "${CHROMEOS}")
(cd dist && sha256sum "${LINUX}.tar.gz" "${WINDOWS}.zip" "${ANDROID}.zip" \
"${MACOS}.zip" "${CHROMEOS}.zip" > SHA256SUMS)
rm -rf "dist/${LINUX}" "dist/${WINDOWS}" "dist/${ANDROID}" "dist/${MACOS}" "dist/${CHROMEOS}"
ls -lh dist ls -lh dist