zahlzerlegung/src/lib/game/tasks.ts

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 } };
}