Harden saved game restore
This commit is contained in:
+41
-12
@@ -1,7 +1,7 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
import type { GameState, QuestionRef } from '$lib/types';
|
||||
import { getQuestionSetById, getTotalQuestions } from '$lib/question-sets';
|
||||
import { findQuestion, getQuestionSetById, getTotalQuestions } from '$lib/question-sets';
|
||||
|
||||
/**
|
||||
* Игровое состояние полностью отделено от компонентов.
|
||||
@@ -32,21 +32,50 @@ function loadState(): GameState {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return initialState();
|
||||
const parsed = JSON.parse(raw) as Partial<GameState>;
|
||||
|
||||
// Защита от повреждённых/чужих данных: проверяем базовую структуру.
|
||||
if (
|
||||
parsed &&
|
||||
(parsed.questionSetId === null || typeof parsed.questionSetId === 'string') &&
|
||||
typeof parsed.score === 'number' &&
|
||||
Array.isArray(parsed.played) &&
|
||||
(parsed.current === null || typeof parsed.current === 'string')
|
||||
!parsed ||
|
||||
(parsed.questionSetId !== null && typeof parsed.questionSetId !== 'string') ||
|
||||
typeof parsed.score !== 'number' ||
|
||||
!Array.isArray(parsed.played) ||
|
||||
(parsed.current !== null && typeof parsed.current !== 'string')
|
||||
) {
|
||||
return {
|
||||
questionSetId: parsed.questionSetId ?? null,
|
||||
score: parsed.score ?? 0,
|
||||
played: parsed.played as QuestionRef[],
|
||||
current: parsed.current ?? null
|
||||
};
|
||||
return initialState();
|
||||
}
|
||||
|
||||
const setId = parsed.questionSetId;
|
||||
// Набор не выбран — экран выбора, возвращаем чистое состояние.
|
||||
if (setId === null) return initialState();
|
||||
|
||||
// Набор неизвестен (удалён/переименован) — полный сброс партии.
|
||||
const set = getQuestionSetById(setId);
|
||||
if (!set) return initialState();
|
||||
|
||||
// Счёт должен быть конечным неотрицательным числом, иначе сброс.
|
||||
if (!Number.isFinite(parsed.score) || parsed.score < 0) return initialState();
|
||||
|
||||
// Оставляем только уникальные ссылки на реально существующие вопросы.
|
||||
const played: QuestionRef[] = [];
|
||||
const seen = new Set<QuestionRef>();
|
||||
for (const ref of parsed.played) {
|
||||
if (typeof ref !== 'string' || seen.has(ref)) continue;
|
||||
if (!findQuestion(set, ref)) continue;
|
||||
seen.add(ref);
|
||||
played.push(ref);
|
||||
}
|
||||
|
||||
// Текущий вопрос сбрасываем, если он не существует или уже сыгран.
|
||||
const current = parsed.current;
|
||||
const safeCurrent =
|
||||
current !== null && findQuestion(set, current) && !seen.has(current) ? current : null;
|
||||
|
||||
return {
|
||||
questionSetId: setId,
|
||||
score: parsed.score,
|
||||
played,
|
||||
current: safeCurrent
|
||||
};
|
||||
} catch {
|
||||
// Повреждённый JSON — просто начинаем заново.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user