293 lines
7.1 KiB
C#
293 lines
7.1 KiB
C#
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);
|
||
}
|
||
} |