1e51ef7def
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/ - 12 code-workspace-Files - STATUS-*.md - viele M/D/R-Änderungen an bereits getrackten Files - .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
129 lines
4.0 KiB
Python
129 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Busfahrt — Zwischenfragen-Generator.
|
|
|
|
Liest cities.json ein, ergänzt pro Stadt ein `question`-Objekt der Form:
|
|
|
|
"question": {
|
|
"prompt": { "de": "In welchem Land liegt Paris?",
|
|
"easy": "In welchem Land liegt Paris?" },
|
|
"choices": ["Frankreich", "Spanien", "Italien"],
|
|
"correctIndex": 0
|
|
}
|
|
|
|
Distraktor-Länder werden deterministisch (mulberry32 auf city.id) aus dem
|
|
Pool aller anderen Länder gewählt — damit Re-Runs dieselben Choices liefern,
|
|
und damit das Daten-File reproduzierbar bleibt.
|
|
|
|
Verwendung:
|
|
python generate-city-questions.py # in-place überschreiben
|
|
python generate-city-questions.py --dry-run # nur ausgeben, was geändert würde
|
|
|
|
Sensationsstädte können später eine zweite Frage als
|
|
"questionExtra": { ... } # hand-kuratiert
|
|
bekommen — dieser Generator überschreibt das Feld nicht.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent / "assets" / "data"
|
|
CITIES_PATH = ROOT / "cities.json"
|
|
|
|
|
|
def mulberry32(seed: int):
|
|
"""Deterministischer RNG (gleicher wie in der Engine), produziert [0, 1)."""
|
|
state = [seed & 0xFFFFFFFF]
|
|
def next_float():
|
|
state[0] = (state[0] + 0x6D2B79F5) & 0xFFFFFFFF
|
|
t = state[0]
|
|
r = ((t ^ (t >> 15)) * (1 | t)) & 0xFFFFFFFF
|
|
r = ((r + ((r ^ (r >> 7)) * (61 | r))) & 0xFFFFFFFF) ^ r
|
|
return ((r ^ (r >> 14)) & 0xFFFFFFFF) / 4294967296.0
|
|
return next_float
|
|
|
|
|
|
def slug_hash(s: str) -> int:
|
|
"""Einfacher String→u32-Hash für deterministisches Seeding."""
|
|
h = 2166136261
|
|
for ch in s:
|
|
h = (h ^ ord(ch)) & 0xFFFFFFFF
|
|
h = (h * 16777619) & 0xFFFFFFFF
|
|
return h
|
|
|
|
|
|
def pick_distractors(city, all_countries):
|
|
"""Wählt 2 plausible Distraktor-Länder, die nicht das eigene Land sind."""
|
|
pool = [c for c in all_countries if c != city["country"]]
|
|
rng = mulberry32(slug_hash(city["id"]))
|
|
# Fisher-Yates-ähnlich, deterministisch
|
|
pool_shuffled = pool.copy()
|
|
for i in range(len(pool_shuffled) - 1, 0, -1):
|
|
j = int(rng() * (i + 1))
|
|
pool_shuffled[i], pool_shuffled[j] = pool_shuffled[j], pool_shuffled[i]
|
|
return pool_shuffled[:2]
|
|
|
|
|
|
def build_question(city, all_countries):
|
|
distractors = pick_distractors(city, all_countries)
|
|
# Position der richtigen Antwort auch deterministisch wählen (0/1/2)
|
|
rng = mulberry32(slug_hash(city["id"] + "_pos"))
|
|
correct_pos = int(rng() * 3)
|
|
choices = distractors[:]
|
|
choices.insert(correct_pos, city["country"])
|
|
title = city["title"]
|
|
return {
|
|
"prompt": {
|
|
"de": f"In welchem Land liegt {title}?",
|
|
"easy": f"In welchem Land liegt {title}?",
|
|
},
|
|
"choices": choices,
|
|
"correctIndex": correct_pos,
|
|
}
|
|
|
|
|
|
def main():
|
|
dry_run = "--dry-run" in sys.argv
|
|
if not CITIES_PATH.exists():
|
|
print(f"ERR: {CITIES_PATH} nicht gefunden", file=sys.stderr)
|
|
return 1
|
|
|
|
with CITIES_PATH.open("r", encoding="utf-8") as f:
|
|
cities = json.load(f)
|
|
|
|
all_countries = sorted({c["country"] for c in cities})
|
|
print(f"Total cities: {len(cities)}")
|
|
print(f"Unique countries: {len(all_countries)}")
|
|
|
|
added = 0
|
|
kept = 0
|
|
for city in cities:
|
|
if "question" in city:
|
|
kept += 1
|
|
continue
|
|
city["question"] = build_question(city, all_countries)
|
|
added += 1
|
|
|
|
print(f"Neu generiert: {added}")
|
|
print(f"Bestehend (nicht überschrieben): {kept}")
|
|
|
|
if dry_run:
|
|
# Sample-Output: erste 3 neuen Fragen
|
|
first = [c for c in cities if "question" in c][:3]
|
|
for c in first:
|
|
print(f"\n{c['id']}:")
|
|
print(json.dumps(c["question"], ensure_ascii=False, indent=2))
|
|
return 0
|
|
|
|
with CITIES_PATH.open("w", encoding="utf-8") as f:
|
|
json.dump(cities, f, ensure_ascii=False, indent=2)
|
|
f.write("\n")
|
|
print(f"OK — {CITIES_PATH} geschrieben.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|