78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
// 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<T>(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 } };
|
|
}
|