EinmaleinsTrainer/ViewModels/QuizViewModel.cs
Naoriel Sa' Rocí 4a4fb54143 Added first code.
Signed-off-by: Naoriel Sa' Rocí <naoriel@sa-roci.de>
2026-03-30 02:26:31 +02:00

293 lines
7.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using EinmaleinsTrainer.Models;
namespace EinmaleinsTrainer.ViewModels;
public class QuizViewModel : ViewModelBase
{
// =========================
// Basisdaten
// =========================
private QuizModel _model = new();
private List<Question> _questions = new();
public List<Question> Questions
{
get => _questions;
private set
{
RxApp.MainThreadScheduler.Schedule(() =>
{
this.RaiseAndSetIfChanged(ref _questions, value);
this.RaisePropertyChanged(nameof(Total));
// Wenn sich Questions ändern → Index resetten
Index = 0;
});
}
}
public int Total => Questions.Count;
private int _index;
public int Index
{
get => _index;
private set
{
RxApp.MainThreadScheduler.Schedule(() =>
{
bool changed = _index == value;
this.RaiseAndSetIfChanged(ref _index, value);
if (!changed)
{
this.RaisePropertyChanged();
}
UpdateDerivedProperties();
});
}
}
// =========================
// Abgeleitete Properties
// =========================
private Question _currentQuestion = new() { A = 0, B = 0 };
public Question CurrentQuestion
{
get => _currentQuestion;
private set
{
RxApp.MainThreadScheduler.Schedule(() =>
{
this.RaiseAndSetIfChanged(ref _currentQuestion, value);
});
}
}
private int _questionNumber;
public int QuestionNumber
{
get => _questionNumber;
private set
{
RxApp.MainThreadScheduler.Schedule(() =>
{
this.RaiseAndSetIfChanged(ref _questionNumber, value);
});
}
}
private string _currentQuestionText = "0 × 0";
public string CurrentQuestionText
{
get => _currentQuestionText;
private set
{
RxApp.MainThreadScheduler.Schedule(() =>
{
this.RaiseAndSetIfChanged(ref _currentQuestionText, value);
});
}
}
// =========================
// User Input / Status
// =========================
private string _userAnswer = "";
public string UserAnswer
{
get => _userAnswer;
set
{
RxApp.MainThreadScheduler.Schedule(() =>
{
this.RaiseAndSetIfChanged(ref _userAnswer, value);
});
}
}
private int _remaining;
public int RemainingSeconds
{
get => _remaining;
private set
{
RxApp.MainThreadScheduler.Schedule(() =>
{
this.RaiseAndSetIfChanged(ref _remaining, value);
if (value > 0)
RedrawRequired?.Invoke(this, new());
});
}
}
private int _score;
public int Score
{
get => _score;
private set
{
RxApp.MainThreadScheduler.Schedule(() =>
{
this.RaiseAndSetIfChanged(ref _score, value);
});
}
}
public bool UseTimer { get; private set; }
private TimeSpan TimeRequired = TimeSpan.Zero;
private DateTime Started = DateTime.MinValue;
// =========================
// Commands / Events
// =========================
public ReactiveCommand<Unit, Unit> SubmitCommand { get; }
private readonly Subject<ResultModel> _finished = new();
public IObservable<ResultModel> Finished => _finished.AsObservable();
public event EventHandler? RedrawRequired;
private IDisposable? _timerSub;
// =========================
// ctor
// =========================
public QuizViewModel()
{
SubmitCommand = ReactiveCommand.Create(
Submit,
outputScheduler: RxApp.MainThreadScheduler
);
}
// =========================
// Init
// =========================
public void Init(QuizModel model)
{
_timerSub?.Dispose();
_model = model;
TimeRequired = TimeSpan.Zero;
Questions = new List<Question>(model.Questions);
Score = 0;
UserAnswer = "";
UseTimer = model.UseTimer && model.Seconds > 1;
RemainingSeconds = UseTimer ? model.Seconds : 0;
// Abgeleitete Properties initial setzen
UpdateDerivedProperties();
if (UseTimer)
StartTimer(model.Seconds);
}
// =========================
// Ableitungen zentral
// =========================
private void UpdateDerivedProperties()
{
if (Questions.Count == 0 || Index < 0 || Index >= Questions.Count)
{
CurrentQuestion = new Question { A = 0, B = 0 };
QuestionNumber = 0;
CurrentQuestionText = "0 × 0";
return;
}
var q = Questions[Index];
CurrentQuestion = q; // Direkt zuweisen
QuestionNumber = Index + 1;
CurrentQuestionText = $"{q.A} × {q.B}";
}
// =========================
// Timer
// =========================
private void StartTimer(int seconds)
{
Started = DateTime.Now;
RemainingSeconds = seconds;
_timerSub?.Dispose();
_timerSub = Observable
.Interval(TimeSpan.FromSeconds(1), RxApp.MainThreadScheduler)
.Select(i => seconds - (int)i - 1)
.TakeWhile(s => s >= 0)
.Subscribe(s =>
{
RemainingSeconds = s;
if (s == 0)
SubmitInternal(false);
});
}
// =========================
// Submit
// =========================
private void Submit()
{
bool correct =
int.TryParse(UserAnswer, out int a) &&
a == CurrentQuestion.CorrectAnswer;
SubmitInternal(correct);
}
private void SubmitInternal(bool correct)
{
if (correct)
RxApp.MainThreadScheduler.Schedule(() => Score++);
_timerSub?.Dispose();
if (UseTimer)
TimeRequired += DateTime.Now - Started;
RxApp.MainThreadScheduler.Schedule(() =>
{
Index++;
if (Index >= Questions.Count)
{
_finished.OnNext(new ResultModel
{
Score = Score,
Total = Total,
TimeRequired = UseTimer ? TimeRequired : null
});
return;
}
// UserAnswer zurücksetzen
UserAnswer = "";
// Update der abgeleiteten Properties (CurrentQuestionText, QuestionNumber)
UpdateDerivedProperties();
RedrawRequired?.Invoke(this, new());
});
if (UseTimer && Index < Questions.Count)
StartTimer(_model.Seconds);
}
}