// Zahlzerlegungs-Aufgabengenerator. // // Eine Aufgabe für Zielzahl T besteht aus: // given: vorgegebene Zerlegungszahl in [0..T] (inkl. der trivialen 0/T-Zerlegung) // answer: T - given (das Kind muss diese tippen) // choices: 3 große Buttons (richtige Antwort + 2 Distraktoren), gemischt // // Aufgabenfolge pro Spieldurchlauf: ein "Beutel" (TaskDeck) zieht alle möglichen // Vorgaben ohne Zurücklegen, mischt nach dem Leeren neu und verhindert direkte // Wiederholungen. So kommt nie 2x dieselbe Vorgabe hintereinander und die // Häufigkeiten bleiben über den Durchlauf maximal gleichmäßig. export type Target = 4 | 5 | 6 | 7 | 8 | 9 | 10; export type Task = { target: Target; given: number; answer: number; choices: number[]; }; export const TARGETS: Target[] = [4, 5, 6, 7, 8, 9, 10]; const DESIRED_CHOICES = 3; function shuffle(arr: T[]): T[] { const a = arr.slice(); for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } // Alle möglichen Vorgabe-Zahlen für eine Zielzahl: 0..target. export function givenPool(target: Target): number[] { const pool: number[] = []; for (let i = 0; i <= target; i++) pool.push(i); return pool; } // Baut die Aufgabe zu einer konkreten Vorgabe (richtige Antwort + 2 zufällige Distraktoren). export function buildTask(target: Target, given: number): Task { const answer = target - given; const distractorPool = givenPool(target).filter((n) => n !== answer); const numDistractors = Math.min(DESIRED_CHOICES - 1, distractorPool.length); const distractors = shuffle(distractorPool).slice(0, numDistractors); const choices = shuffle([answer, ...distractors]); return { target, given, answer, choices }; } // Ziehbeutel pro Spieldurchlauf. `bag` = noch nicht gezogene Vorgaben des aktuellen // Zyklus, `lastGiven` = zuletzt gezogene Vorgabe (für den Schutz an der Zyklusgrenze). export type TaskDeck = { target: Target; bag: number[]; lastGiven: number | null; }; export function createDeck(target: Target): TaskDeck { return { target, bag: [], lastGiven: null }; } // Zieht die nächste Aufgabe und liefert das fortgeschriebene Deck zurück (immutable). export function drawTask(deck: TaskDeck): { task: Task; deck: TaskDeck } { let bag = deck.bag.slice(); if (bag.length === 0) { bag = shuffle(givenPool(deck.target)); // Zyklusgrenze: erste Ziehung darf nicht der letzten des alten Beutels gleichen. if (deck.lastGiven !== null && bag.length > 1 && bag[0] === deck.lastGiven) { [bag[0], bag[1]] = [bag[1], bag[0]]; } } const given = bag.shift() as number; const task = buildTask(deck.target, given); return { task, deck: { target: deck.target, bag, lastGiven: given } }; }