Gegen blindes Durchtippen: vierte Antwortmöglichkeit + kurze Denkpause nach falscher Antwort
This commit is contained in:
parent
ad1ba740f8
commit
34bcef5da8
|
|
@ -9,9 +9,13 @@
|
|||
|
||||
let wrongValue = $state<number | null>(null);
|
||||
let lockedCorrect = $state<number | null>(null);
|
||||
// Kurze Denkpause nach falscher Antwort: Knöpfe sind ~0,9s gesperrt und gedimmt.
|
||||
// Kein Punktverlust — macht blindes Durchtippen nur langsamer als Nachdenken.
|
||||
let coolingDown = $state(false);
|
||||
let cooldownTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function handle(value: number) {
|
||||
if (disabled || lockedCorrect !== null) return;
|
||||
if (disabled || coolingDown || lockedCorrect !== null) return;
|
||||
if (value === correctAnswer) {
|
||||
lockedCorrect = value;
|
||||
onAnswer(value);
|
||||
|
|
@ -19,21 +23,34 @@
|
|||
} else {
|
||||
wrongValue = value;
|
||||
onAnswer(value);
|
||||
setTimeout(() => {
|
||||
if (wrongValue === value) wrongValue = null;
|
||||
}, 380);
|
||||
coolingDown = true;
|
||||
if (cooldownTimer) clearTimeout(cooldownTimer);
|
||||
cooldownTimer = setTimeout(() => {
|
||||
coolingDown = false;
|
||||
wrongValue = null;
|
||||
}, 900);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset bei Aufgabenwechsel
|
||||
// Reset NUR bei echtem Aufgabenwechsel. Der Effekt feuert über den choices-Prop auch
|
||||
// bei jeder Falsch-Antwort (die den game-Store ändert) — daher per Referenzvergleich
|
||||
// absichern, sonst würde die Denkpause sofort wieder aufgehoben.
|
||||
let prevChoices: number[] | null = null;
|
||||
$effect(() => {
|
||||
void choices; // dependency
|
||||
lockedCorrect = null;
|
||||
wrongValue = null;
|
||||
if (choices !== prevChoices) {
|
||||
prevChoices = choices;
|
||||
lockedCorrect = null;
|
||||
wrongValue = null;
|
||||
coolingDown = false;
|
||||
if (cooldownTimer) {
|
||||
clearTimeout(cooldownTimer);
|
||||
cooldownTimer = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="answers" role="group" aria-label="Antwortmöglichkeiten">
|
||||
<div class="answers" class:cooling={coolingDown} role="group" aria-label="Antwortmöglichkeiten">
|
||||
{#each choices as value (value)}
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -42,7 +59,7 @@
|
|||
class:correct={lockedCorrect === value}
|
||||
onclick={() => handle(value)}
|
||||
aria-label={`Antwort ${value}`}
|
||||
{disabled}
|
||||
disabled={disabled || coolingDown}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
|
|
@ -66,12 +83,16 @@
|
|||
color: var(--c-text);
|
||||
border-radius: 22px;
|
||||
box-shadow: 0 6px 0 #b9c4dc;
|
||||
transition: transform 0.08s ease, background 0.15s ease, box-shadow 0.08s ease;
|
||||
transition: transform 0.08s ease, background 0.15s ease, box-shadow 0.08s ease, opacity 0.2s ease;
|
||||
}
|
||||
.answer:active:not(:disabled) {
|
||||
transform: translateY(4px);
|
||||
box-shadow: 0 2px 0 #b9c4dc;
|
||||
}
|
||||
/* Während der Denkpause die übrigen Knöpfe sanft dimmen (der falsche bleibt rot sichtbar). */
|
||||
.answers.cooling .answer:not(.wrong) {
|
||||
opacity: 0.45;
|
||||
}
|
||||
.answer.wrong {
|
||||
animation: shake 0.36s ease;
|
||||
background: #ffd0d0;
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ describe('buildTask', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('returns 3 choices for every target', () => {
|
||||
it('returns 4 choices for every target', () => {
|
||||
for (const target of TARGETS) {
|
||||
const task = buildTask(target, 1);
|
||||
expect(task.choices.length).toBe(3);
|
||||
expect(task.choices.length).toBe(4);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// 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
|
||||
// choices: 4 große Buttons (richtige Antwort + 3 Distraktoren), gemischt
|
||||
//
|
||||
// Aufgabenfolge pro Spieldurchlauf: ein "Beutel" (TaskDeck) zieht alle möglichen
|
||||
// Vorgaben ohne Zurücklegen, mischt nach dem Leeren neu und verhindert direkte
|
||||
|
|
@ -21,7 +21,7 @@ export type Task = {
|
|||
|
||||
export const TARGETS: Target[] = [4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
const DESIRED_CHOICES = 3;
|
||||
const DESIRED_CHOICES = 4;
|
||||
|
||||
function shuffle<T>(arr: T[]): T[] {
|
||||
const a = arr.slice();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user