#!/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())