Harden saved game restore
This commit is contained in:
+41
-12
@@ -1,7 +1,7 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import type { GameState, QuestionRef } from '$lib/types';
|
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);
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
if (!raw) return initialState();
|
if (!raw) return initialState();
|
||||||
const parsed = JSON.parse(raw) as Partial<GameState>;
|
const parsed = JSON.parse(raw) as Partial<GameState>;
|
||||||
|
|
||||||
// Защита от повреждённых/чужих данных: проверяем базовую структуру.
|
// Защита от повреждённых/чужих данных: проверяем базовую структуру.
|
||||||
if (
|
if (
|
||||||
parsed &&
|
!parsed ||
|
||||||
(parsed.questionSetId === null || typeof parsed.questionSetId === 'string') &&
|
(parsed.questionSetId !== null && typeof parsed.questionSetId !== 'string') ||
|
||||||
typeof parsed.score === 'number' &&
|
typeof parsed.score !== 'number' ||
|
||||||
Array.isArray(parsed.played) &&
|
!Array.isArray(parsed.played) ||
|
||||||
(parsed.current === null || typeof parsed.current === 'string')
|
(parsed.current !== null && typeof parsed.current !== 'string')
|
||||||
) {
|
) {
|
||||||
return {
|
return initialState();
|
||||||
questionSetId: parsed.questionSetId ?? null,
|
|
||||||
score: parsed.score ?? 0,
|
|
||||||
played: parsed.played as QuestionRef[],
|
|
||||||
current: parsed.current ?? null
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
} catch {
|
||||||
// Повреждённый JSON — просто начинаем заново.
|
// Повреждённый JSON — просто начинаем заново.
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user