108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
|
|
"""Bank poleceń do użycia na urządzeniu (ONDEVICE §4.1)."""
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import json
|
|||
|
|
import random
|
|||
|
|
import sys
|
|||
|
|
from itertools import product
|
|||
|
|
from typing import Optional
|
|||
|
|
|
|||
|
|
from .generator import Clause, Command, random_command, render
|
|||
|
|
from .lexicon import COLORS, LOCATIONS, OBJECTS, VERBS
|
|||
|
|
|
|||
|
|
# Rozmiary przestrzeni:
|
|||
|
|
# Poziom 1: 2 × 10 × 6 = 120 unikatów
|
|||
|
|
# Poziom 2: 2 × 10 × 6 × 6 = 720 unikatów
|
|||
|
|
# Poziom 3: ~635 040 unikatów → generujemy losowo z deduplikacją
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _enumerate_level1() -> list[str]:
|
|||
|
|
texts = []
|
|||
|
|
for v, o, loc in product(VERBS, OBJECTS, LOCATIONS):
|
|||
|
|
texts.append(render(Command([Clause(v, o, loc, None)], 1)))
|
|||
|
|
return texts
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _enumerate_level2() -> list[str]:
|
|||
|
|
texts = []
|
|||
|
|
for v, o, loc, c in product(VERBS, OBJECTS, LOCATIONS, COLORS):
|
|||
|
|
texts.append(render(Command([Clause(v, o, loc, c)], 2)))
|
|||
|
|
return texts
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _random_unique_level3(target: int) -> list[str]:
|
|||
|
|
seen: set[str] = set()
|
|||
|
|
results: list[str] = []
|
|||
|
|
consecutive_misses = 0
|
|||
|
|
while len(results) < target and consecutive_misses < 500:
|
|||
|
|
text = render(random_command(3))
|
|||
|
|
if text not in seen:
|
|||
|
|
seen.add(text)
|
|||
|
|
results.append(text)
|
|||
|
|
consecutive_misses = 0
|
|||
|
|
else:
|
|||
|
|
consecutive_misses += 1
|
|||
|
|
return results
|
|||
|
|
|
|||
|
|
|
|||
|
|
def generate_bank(per_level: int, seed: Optional[int] = None) -> dict:
|
|||
|
|
"""Zwraca słownik gotowy do zrzutu jako JSON.
|
|||
|
|
|
|||
|
|
Dla poziomów 1 i 2 wylicza wyczerpująco wszystkie kombinacje i tasuje;
|
|||
|
|
dla poziomu 3 generuje losowo z deduplikacją.
|
|||
|
|
Jeśli przestrzeń jest mniejsza niż per_level, zwraca tyle ile możliwe.
|
|||
|
|
"""
|
|||
|
|
if seed is not None:
|
|||
|
|
random.seed(seed)
|
|||
|
|
|
|||
|
|
commands: list[dict] = []
|
|||
|
|
|
|||
|
|
for level, pool_fn in [(1, _enumerate_level1), (2, _enumerate_level2)]:
|
|||
|
|
pool = pool_fn()
|
|||
|
|
random.shuffle(pool)
|
|||
|
|
for text in pool[:per_level]:
|
|||
|
|
commands.append({"level": level, "text": text})
|
|||
|
|
|
|||
|
|
for text in _random_unique_level3(per_level):
|
|||
|
|
commands.append({"level": 3, "text": text})
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"meta": {"version": "1", "per_level": per_level},
|
|||
|
|
"commands": commands,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def run_export(argv: list[str]) -> None:
|
|||
|
|
parser = argparse.ArgumentParser(
|
|||
|
|
prog="python -m ipin_vr export",
|
|||
|
|
description="Generuj bank poleceń do JSON (ONDEVICE §4.1)",
|
|||
|
|
)
|
|||
|
|
parser.add_argument(
|
|||
|
|
"--per-level", type=int, default=300, metavar="N",
|
|||
|
|
help="liczba unikatów na poziom (domyślnie: 300)",
|
|||
|
|
)
|
|||
|
|
parser.add_argument(
|
|||
|
|
"--out", default="-", metavar="PATH",
|
|||
|
|
help="ścieżka pliku wyjściowego (- = stdout)",
|
|||
|
|
)
|
|||
|
|
parser.add_argument(
|
|||
|
|
"--seed", type=int, metavar="N",
|
|||
|
|
help="seed generatora losowego",
|
|||
|
|
)
|
|||
|
|
args = parser.parse_args(argv)
|
|||
|
|
|
|||
|
|
bank = generate_bank(per_level=args.per_level, seed=args.seed)
|
|||
|
|
output = json.dumps(bank, ensure_ascii=False, indent=2)
|
|||
|
|
|
|||
|
|
if args.out == "-":
|
|||
|
|
print(output)
|
|||
|
|
else:
|
|||
|
|
with open(args.out, "w", encoding="utf-8") as f:
|
|||
|
|
f.write(output)
|
|||
|
|
by_level = {}
|
|||
|
|
for cmd in bank["commands"]:
|
|||
|
|
by_level[cmd["level"]] = by_level.get(cmd["level"], 0) + 1
|
|||
|
|
summary = ", ".join(f"L{k}:{v}" for k, v in sorted(by_level.items()))
|
|||
|
|
total = len(bank["commands"])
|
|||
|
|
print(f"Zapisano {total} poleceń → {args.out} ({summary})", file=sys.stderr)
|