Fix export on fresh clone (make import), add elision and duplicate-sentence guards, replace 14 duplicate sentences (0.5.3)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-12 10:18:20 +02:00
parent 6625342163
commit 9978a294db
23 changed files with 331 additions and 84 deletions
+110
View File
@@ -0,0 +1,110 @@
extends GutTest
## Garde-fou orthographique sur tous les textes du jeu : « le arbre » doit
## s'ecrire « l'arbre ». Parcourt tous les JSON de data/ (chaines et listes de
## mots consecutifs) a la recherche d'une elision manquante. Les mots a « h »
## aspire et quelques exceptions legitimes sont listes ci-dessous.
const DATA_DIR := "res://data"
## Mots a « h » aspire ou vocalique sans elision : « le hibou », « le onze ».
const NO_ELISION_WORDS: Array[String] = [
"hibou",
"haie",
"haies",
"héros",
"hérisson",
"hamac",
"hanche",
"hangar",
"haricot",
"haricots",
"hasard",
"hache",
"hauteur",
"haut",
"haute",
"hautes",
"hauts",
"hall",
"hameau",
"harpe",
"hâte",
"hérissons",
"hurlement",
"huit",
"onze",
"oui",
"ouistiti",
"yaourt",
"yeux",
"ukulélé",
]
## (*UCP) : \\w couvre les lettres accentuees. Motif : mot d'elision, espaces,
## puis mot a voyelle ou « h » initial ; l'assertion arriere ecarte « écris-la en ».
const PATTERN := (
"(*UCP)(?<![-\\w'’])(?i)(le|la|de|que|ne|se|me|te|je|ce)\\s+"
+ "([aeiouâàäéèêëîïôöûùüœh][\\wàâäéèêëîïôöûùüçœ]*)"
)
var _regex := RegEx.new()
var _problems: Array[String] = []
func before_all() -> void:
_regex.compile(PATTERN)
func test_no_missing_elision_in_game_texts() -> void:
_scan_dir(DATA_DIR)
assert_eq(_problems.size(), 0, "Elisions manquantes : %s" % "\n".join(_problems))
func test_detector_catches_a_missing_elision() -> void:
_problems.clear()
_check_text("le arbre est grand", "exemple")
assert_eq(_problems.size(), 1)
_problems.clear()
_check_text("Le hibou dort. Écris-la en entier. Le onze.", "exemple")
assert_eq(_problems.size(), 0)
func _scan_dir(path: String) -> void:
var dir := DirAccess.open(path)
if dir == null:
return
for sub in dir.get_directories():
if sub != "schemas":
_scan_dir(path.path_join(sub))
for file_name in dir.get_files():
if file_name.ends_with(".json"):
_scan_file(path.path_join(file_name))
func _scan_file(path: String) -> void:
var file := FileAccess.open(path, FileAccess.READ)
var parsed: Variant = JSON.parse_string(file.get_as_text())
_scan_value(parsed, path)
func _scan_value(value: Variant, where: String) -> void:
if typeof(value) == TYPE_STRING:
_check_text(value, where)
elif typeof(value) == TYPE_DICTIONARY:
for key: Variant in value:
_scan_value(value[key], where)
elif typeof(value) == TYPE_ARRAY:
var items: Array = value
# Mots consecutifs d'une phrase decoupee en jetons.
for i in range(items.size() - 1):
if typeof(items[i]) == TYPE_STRING and typeof(items[i + 1]) == TYPE_STRING:
_check_text("%s %s" % [items[i], items[i + 1]], where)
for item: Variant in items:
_scan_value(item, where)
func _check_text(text: String, where: String) -> void:
for match in _regex.search_all(text):
var next_word := match.get_string(2).to_lower()
if NO_ELISION_WORDS.has(next_word):
continue
_problems.append("%s : « %s »" % [where, match.get_string(0)])
+1
View File
@@ -0,0 +1 @@
uid://beh3tst4how7j
+77
View File
@@ -0,0 +1,77 @@
extends GutTest
## Aucune phrase ne doit apparaitre deux fois dans le jeu (exercices et
## dictees confondus) : un meme enonce revu dans deux missions est une
## redite, pas un nouvel exercice. La phrase est reconstituee en version
## correcte (blanc rempli, mot fautif corrige) puis comparee sans casse ni
## ponctuation.
const EXERCISES_DIR := "res://data/exercises"
const DICTATIONS_DIR := "res://data/dictations"
func test_no_sentence_is_used_twice() -> void:
var seen: Dictionary = {}
var duplicates: Array[String] = []
for entry in _collect_sentences():
var key := _normalize(entry["sentence"])
if seen.has(key):
duplicates.append("« %s » : %s et %s" % [entry["sentence"], seen[key], entry["id"]])
else:
seen[key] = entry["id"]
assert_eq(duplicates.size(), 0, "Phrases en double :\n%s" % "\n".join(duplicates))
assert_gt(seen.size(), 100, "le test doit avoir vu les phrases du jeu")
func _collect_sentences() -> Array[Dictionary]:
var sentences: Array[Dictionary] = []
for path in _json_files(EXERCISES_DIR):
var data := _read(path)
var sentence := ""
if data.get("type", "") == "multiple_choice":
var answers: Array = data["answers"]
sentence = str(data["question"]).replace(
"...", str(answers[int(data["correct_answer"])])
)
elif data.get("type", "") == "sentence_correction":
var words: Array[String] = []
for token: Dictionary in data["tokens"]:
words.append(token["text"] if token["correct"] else token["expected"])
sentence = " ".join(words)
if not sentence.is_empty():
sentences.append({"id": data["id"], "sentence": sentence})
for path in _json_files(DICTATIONS_DIR):
var data := _read(path)
sentences.append({"id": data["id"], "sentence": data["target_text"]})
return sentences
func _normalize(sentence: String) -> String:
var cleaned := ""
for character in sentence.to_lower():
if character.is_valid_int() or character == "'" or character == " ":
cleaned += character
elif character.to_upper() != character or "éèêàâîïôûùçœ".contains(character):
cleaned += character
else:
cleaned += " "
var parts := cleaned.split(" ", false)
return " ".join(parts)
func _json_files(path: String) -> Array[String]:
var found: Array[String] = []
var dir := DirAccess.open(path)
if dir == null:
return found
for sub in dir.get_directories():
found.append_array(_json_files(path.path_join(sub)))
for file_name in dir.get_files():
if file_name.ends_with(".json"):
found.append(path.path_join(file_name))
return found
func _read(path: String) -> Dictionary:
var file := FileAccess.open(path, FileAccess.READ)
var parsed: Variant = JSON.parse_string(file.get_as_text())
return parsed if typeof(parsed) == TYPE_DICTIONARY else {}
@@ -0,0 +1 @@
uid://cekmrk80y81fk