Files
cmr-mini/miniprogram/game/core/sessionRecovery.ts

147 lines
4.7 KiB
TypeScript

import { type LonLatPoint } from '../../utils/projection'
import { type GameLaunchEnvelope } from '../../utils/gameLaunch'
import { type GameSessionState } from './gameSessionState'
export interface RecoveryTelemetrySnapshot {
distanceMeters: number
currentSpeedKmh: number | null
averageSpeedKmh: number | null
heartRateBpm: number | null
caloriesKcal: number | null
lastGpsPoint: LonLatPoint | null
lastGpsAt: number | null
lastGpsAccuracyMeters: number | null
}
export interface RecoveryViewportSnapshot {
zoom: number
centerTileX: number
centerTileY: number
rotationDeg: number
gpsLockEnabled: boolean
hasGpsCenteredOnce: boolean
}
export interface RecoveryRuntimeSnapshot {
gameState: GameSessionState
telemetry: RecoveryTelemetrySnapshot
viewport: RecoveryViewportSnapshot
currentGpsPoint: LonLatPoint | null
currentGpsAccuracyMeters: number | null
currentGpsInsideMap: boolean
bonusScore: number
quizCorrectCount: number
quizWrongCount: number
quizTimeoutCount: number
}
export interface SessionRecoverySnapshot {
schemaVersion: 1
savedAt: number
launchEnvelope: GameLaunchEnvelope
configAppId: string
configVersion: string
runtime: RecoveryRuntimeSnapshot
}
const SESSION_RECOVERY_STORAGE_KEY = 'cmr.sessionRecovery.v1'
function cloneLonLatPoint(point: LonLatPoint | null): LonLatPoint | null {
if (!point) {
return null
}
return {
lon: point.lon,
lat: point.lat,
}
}
function cloneGameSessionState(state: GameSessionState): GameSessionState {
return {
status: state.status,
endReason: state.endReason,
startedAt: state.startedAt,
endedAt: state.endedAt,
completedControlIds: state.completedControlIds.slice(),
skippedControlIds: state.skippedControlIds.slice(),
currentTargetControlId: state.currentTargetControlId,
inRangeControlId: state.inRangeControlId,
score: state.score,
guidanceState: state.guidanceState,
modeState: state.modeState
? JSON.parse(JSON.stringify(state.modeState)) as Record<string, unknown>
: null,
}
}
export function cloneSessionRecoverySnapshot(snapshot: SessionRecoverySnapshot): SessionRecoverySnapshot {
return {
schemaVersion: 1,
savedAt: snapshot.savedAt,
launchEnvelope: JSON.parse(JSON.stringify(snapshot.launchEnvelope)) as GameLaunchEnvelope,
configAppId: snapshot.configAppId,
configVersion: snapshot.configVersion,
runtime: {
gameState: cloneGameSessionState(snapshot.runtime.gameState),
telemetry: {
distanceMeters: snapshot.runtime.telemetry.distanceMeters,
currentSpeedKmh: snapshot.runtime.telemetry.currentSpeedKmh,
averageSpeedKmh: snapshot.runtime.telemetry.averageSpeedKmh,
heartRateBpm: snapshot.runtime.telemetry.heartRateBpm,
caloriesKcal: snapshot.runtime.telemetry.caloriesKcal,
lastGpsPoint: cloneLonLatPoint(snapshot.runtime.telemetry.lastGpsPoint),
lastGpsAt: snapshot.runtime.telemetry.lastGpsAt,
lastGpsAccuracyMeters: snapshot.runtime.telemetry.lastGpsAccuracyMeters,
},
viewport: {
zoom: snapshot.runtime.viewport.zoom,
centerTileX: snapshot.runtime.viewport.centerTileX,
centerTileY: snapshot.runtime.viewport.centerTileY,
rotationDeg: snapshot.runtime.viewport.rotationDeg,
gpsLockEnabled: snapshot.runtime.viewport.gpsLockEnabled,
hasGpsCenteredOnce: snapshot.runtime.viewport.hasGpsCenteredOnce,
},
currentGpsPoint: cloneLonLatPoint(snapshot.runtime.currentGpsPoint),
currentGpsAccuracyMeters: snapshot.runtime.currentGpsAccuracyMeters,
currentGpsInsideMap: snapshot.runtime.currentGpsInsideMap,
bonusScore: snapshot.runtime.bonusScore,
quizCorrectCount: snapshot.runtime.quizCorrectCount,
quizWrongCount: snapshot.runtime.quizWrongCount,
quizTimeoutCount: snapshot.runtime.quizTimeoutCount,
},
}
}
function normalizeSessionRecoverySnapshot(raw: unknown): SessionRecoverySnapshot | null {
if (!raw || typeof raw !== 'object') {
return null
}
const candidate = raw as SessionRecoverySnapshot
if (candidate.schemaVersion !== 1 || !candidate.runtime || !candidate.runtime.gameState) {
return null
}
return cloneSessionRecoverySnapshot(candidate)
}
export function loadSessionRecoverySnapshot(): SessionRecoverySnapshot | null {
try {
return normalizeSessionRecoverySnapshot(wx.getStorageSync(SESSION_RECOVERY_STORAGE_KEY))
} catch {
return null
}
}
export function saveSessionRecoverySnapshot(snapshot: SessionRecoverySnapshot): void {
try {
wx.setStorageSync(SESSION_RECOVERY_STORAGE_KEY, cloneSessionRecoverySnapshot(snapshot))
} catch {}
}
export function clearSessionRecoverySnapshot(): void {
try {
wx.removeStorageSync(SESSION_RECOVERY_STORAGE_KEY)
} catch {}
}