refactor: 收口拼图 runtime 状态合并
This commit is contained in:
@@ -589,6 +589,7 @@ import {
|
||||
buildPuzzleResultProfileId,
|
||||
buildPuzzleResultWorkId,
|
||||
} from './platformPuzzleIdentityModel';
|
||||
import { mergePuzzleServiceRuntimeState } from './platformPuzzleRuntimeStateModel';
|
||||
import {
|
||||
type PlatformPuzzleRuntimeAuthMode,
|
||||
resolvePlatformRecommendRuntimeAuthPlan,
|
||||
@@ -1250,45 +1251,6 @@ function CreationResultRecoveryPanel({
|
||||
);
|
||||
}
|
||||
|
||||
function mergePuzzleServiceRuntimeState(
|
||||
currentRun: PuzzleRunSnapshot,
|
||||
serviceRun: PuzzleRunSnapshot,
|
||||
): PuzzleRunSnapshot {
|
||||
if (!currentRun.currentLevel || !serviceRun.currentLevel) {
|
||||
return currentRun;
|
||||
}
|
||||
|
||||
const serviceLevel = serviceRun.currentLevel;
|
||||
const leaderboardEntries =
|
||||
serviceLevel.leaderboardEntries.length > 0
|
||||
? serviceLevel.leaderboardEntries
|
||||
: serviceRun.leaderboardEntries;
|
||||
|
||||
// 中文注释:拼块布局和通关状态由前端即时裁决;后端快照只合并榜单与下一关 handoff。
|
||||
return {
|
||||
...currentRun,
|
||||
runId: serviceRun.runId,
|
||||
entryProfileId: serviceRun.entryProfileId,
|
||||
clearedLevelCount: Math.max(
|
||||
currentRun.clearedLevelCount,
|
||||
serviceRun.clearedLevelCount,
|
||||
),
|
||||
recommendedNextProfileId: serviceRun.recommendedNextProfileId,
|
||||
nextLevelMode: serviceRun.nextLevelMode,
|
||||
nextLevelProfileId: serviceRun.nextLevelProfileId,
|
||||
nextLevelId: serviceRun.nextLevelId,
|
||||
recommendedNextWorks: serviceRun.recommendedNextWorks,
|
||||
leaderboardEntries,
|
||||
currentLevel: {
|
||||
...currentRun.currentLevel,
|
||||
leaderboardEntries:
|
||||
leaderboardEntries.length > 0
|
||||
? leaderboardEntries
|
||||
: currentRun.currentLevel.leaderboardEntries,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function PlatformEntryFlowShellImpl({
|
||||
selectionStage,
|
||||
setSelectionStage,
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import type {
|
||||
PuzzleLeaderboardEntry,
|
||||
PuzzleRunSnapshot,
|
||||
PuzzleRuntimeLevelSnapshot,
|
||||
} from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
|
||||
import { mergePuzzleServiceRuntimeState } from './platformPuzzleRuntimeStateModel';
|
||||
|
||||
const currentLeaderboard: PuzzleLeaderboardEntry[] = [
|
||||
{
|
||||
rank: 1,
|
||||
nickname: '本地玩家',
|
||||
elapsedMs: 12000,
|
||||
isCurrentPlayer: true,
|
||||
},
|
||||
];
|
||||
|
||||
const serviceLevelLeaderboard: PuzzleLeaderboardEntry[] = [
|
||||
{
|
||||
rank: 1,
|
||||
nickname: '服务端玩家',
|
||||
elapsedMs: 9000,
|
||||
},
|
||||
];
|
||||
|
||||
const serviceRunLeaderboard: PuzzleLeaderboardEntry[] = [
|
||||
{
|
||||
rank: 2,
|
||||
nickname: '全局玩家',
|
||||
elapsedMs: 15000,
|
||||
},
|
||||
];
|
||||
|
||||
function buildPuzzleLevel(
|
||||
overrides: Partial<PuzzleRuntimeLevelSnapshot> = {},
|
||||
): PuzzleRuntimeLevelSnapshot {
|
||||
return {
|
||||
runId: 'run-current',
|
||||
levelIndex: 0,
|
||||
levelId: 'level-1',
|
||||
gridSize: 3,
|
||||
profileId: 'puzzle-profile-current',
|
||||
levelName: '星桥机关',
|
||||
authorDisplayName: '玩家',
|
||||
themeTags: ['星桥'],
|
||||
coverImageSrc: '/cover.png',
|
||||
board: {
|
||||
rows: 3,
|
||||
cols: 3,
|
||||
pieces: [],
|
||||
mergedGroups: [],
|
||||
selectedPieceId: null,
|
||||
allTilesResolved: true,
|
||||
},
|
||||
status: 'cleared',
|
||||
startedAtMs: 1000,
|
||||
clearedAtMs: 13000,
|
||||
elapsedMs: 12000,
|
||||
timeLimitMs: 120000,
|
||||
remainingMs: 108000,
|
||||
pausedAccumulatedMs: 0,
|
||||
pauseStartedAtMs: null,
|
||||
freezeAccumulatedMs: 0,
|
||||
freezeStartedAtMs: null,
|
||||
freezeUntilMs: null,
|
||||
leaderboardEntries: currentLeaderboard,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPuzzleRun(
|
||||
overrides: Partial<PuzzleRunSnapshot> = {},
|
||||
): PuzzleRunSnapshot {
|
||||
return {
|
||||
runId: 'run-current',
|
||||
entryProfileId: 'puzzle-profile-current',
|
||||
clearedLevelCount: 1,
|
||||
currentLevelIndex: 0,
|
||||
currentGridSize: 3,
|
||||
playedProfileIds: ['puzzle-profile-current'],
|
||||
previousLevelTags: ['星桥'],
|
||||
currentLevel: buildPuzzleLevel(),
|
||||
recommendedNextProfileId: null,
|
||||
nextLevelMode: 'sameWork',
|
||||
nextLevelProfileId: null,
|
||||
nextLevelId: null,
|
||||
recommendedNextWorks: [],
|
||||
leaderboardEntries: currentLeaderboard,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('platformPuzzleRuntimeStateModel', () => {
|
||||
test('keeps current run when either current level is missing', () => {
|
||||
const currentRun = buildPuzzleRun({ currentLevel: null });
|
||||
expect(
|
||||
mergePuzzleServiceRuntimeState(currentRun, buildPuzzleRun()),
|
||||
).toBe(currentRun);
|
||||
|
||||
const serviceRun = buildPuzzleRun({ currentLevel: null });
|
||||
const playableCurrentRun = buildPuzzleRun();
|
||||
expect(
|
||||
mergePuzzleServiceRuntimeState(playableCurrentRun, serviceRun),
|
||||
).toBe(playableCurrentRun);
|
||||
});
|
||||
|
||||
test('merges service leaderboard and next-level handoff without replacing local level state', () => {
|
||||
const currentRun = buildPuzzleRun({
|
||||
clearedLevelCount: 2,
|
||||
currentLevel: buildPuzzleLevel({
|
||||
runId: 'run-current',
|
||||
status: 'cleared',
|
||||
board: {
|
||||
rows: 3,
|
||||
cols: 3,
|
||||
pieces: [
|
||||
{
|
||||
pieceId: 'piece-local',
|
||||
correctRow: 0,
|
||||
correctCol: 0,
|
||||
currentRow: 0,
|
||||
currentCol: 0,
|
||||
mergedGroupId: null,
|
||||
},
|
||||
],
|
||||
mergedGroups: [],
|
||||
selectedPieceId: 'piece-local',
|
||||
allTilesResolved: true,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const serviceRun = buildPuzzleRun({
|
||||
runId: 'run-service',
|
||||
entryProfileId: 'puzzle-profile-service',
|
||||
clearedLevelCount: 1,
|
||||
recommendedNextProfileId: 'next-recommended',
|
||||
nextLevelMode: 'similarWorks',
|
||||
nextLevelProfileId: 'next-profile',
|
||||
nextLevelId: 'next-level',
|
||||
recommendedNextWorks: [
|
||||
{
|
||||
profileId: 'next-profile',
|
||||
levelName: '月桥机关',
|
||||
authorDisplayName: '推荐作者',
|
||||
themeTags: ['月桥'],
|
||||
coverImageSrc: '/next-cover.png',
|
||||
similarityScore: 0.91,
|
||||
},
|
||||
],
|
||||
currentLevel: buildPuzzleLevel({
|
||||
runId: 'run-service-level',
|
||||
status: 'playing',
|
||||
leaderboardEntries: serviceLevelLeaderboard,
|
||||
}),
|
||||
});
|
||||
|
||||
const merged = mergePuzzleServiceRuntimeState(currentRun, serviceRun);
|
||||
|
||||
expect(merged.runId).toBe('run-service');
|
||||
expect(merged.entryProfileId).toBe('puzzle-profile-service');
|
||||
expect(merged.clearedLevelCount).toBe(2);
|
||||
expect(merged.recommendedNextProfileId).toBe('next-recommended');
|
||||
expect(merged.nextLevelMode).toBe('similarWorks');
|
||||
expect(merged.nextLevelProfileId).toBe('next-profile');
|
||||
expect(merged.nextLevelId).toBe('next-level');
|
||||
expect(merged.recommendedNextWorks).toEqual(serviceRun.recommendedNextWorks);
|
||||
expect(merged.leaderboardEntries).toEqual(serviceLevelLeaderboard);
|
||||
expect(merged.currentLevel?.status).toBe('cleared');
|
||||
expect(merged.currentLevel?.board.pieces).toEqual(
|
||||
currentRun.currentLevel?.board.pieces,
|
||||
);
|
||||
expect(merged.currentLevel?.leaderboardEntries).toEqual(
|
||||
serviceLevelLeaderboard,
|
||||
);
|
||||
});
|
||||
|
||||
test('falls back to service run leaderboard, then current level leaderboard', () => {
|
||||
const currentRun = buildPuzzleRun();
|
||||
const serviceRun = buildPuzzleRun({
|
||||
currentLevel: buildPuzzleLevel({ leaderboardEntries: [] }),
|
||||
leaderboardEntries: serviceRunLeaderboard,
|
||||
});
|
||||
|
||||
expect(
|
||||
mergePuzzleServiceRuntimeState(currentRun, serviceRun).currentLevel
|
||||
?.leaderboardEntries,
|
||||
).toEqual(serviceRunLeaderboard);
|
||||
|
||||
expect(
|
||||
mergePuzzleServiceRuntimeState(currentRun, {
|
||||
...serviceRun,
|
||||
leaderboardEntries: [],
|
||||
}).currentLevel?.leaderboardEntries,
|
||||
).toEqual(currentLeaderboard);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { PuzzleRunSnapshot } from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
|
||||
|
||||
export function mergePuzzleServiceRuntimeState(
|
||||
currentRun: PuzzleRunSnapshot,
|
||||
serviceRun: PuzzleRunSnapshot,
|
||||
): PuzzleRunSnapshot {
|
||||
if (!currentRun.currentLevel || !serviceRun.currentLevel) {
|
||||
return currentRun;
|
||||
}
|
||||
|
||||
const serviceLevel = serviceRun.currentLevel;
|
||||
const leaderboardEntries =
|
||||
serviceLevel.leaderboardEntries.length > 0
|
||||
? serviceLevel.leaderboardEntries
|
||||
: serviceRun.leaderboardEntries;
|
||||
|
||||
// 中文注释:拼块布局和通关状态由前端即时裁决;后端快照只合并榜单与下一关 handoff。
|
||||
return {
|
||||
...currentRun,
|
||||
runId: serviceRun.runId,
|
||||
entryProfileId: serviceRun.entryProfileId,
|
||||
clearedLevelCount: Math.max(
|
||||
currentRun.clearedLevelCount,
|
||||
serviceRun.clearedLevelCount,
|
||||
),
|
||||
recommendedNextProfileId: serviceRun.recommendedNextProfileId,
|
||||
nextLevelMode: serviceRun.nextLevelMode,
|
||||
nextLevelProfileId: serviceRun.nextLevelProfileId,
|
||||
nextLevelId: serviceRun.nextLevelId,
|
||||
recommendedNextWorks: serviceRun.recommendedNextWorks,
|
||||
leaderboardEntries,
|
||||
currentLevel: {
|
||||
...currentRun.currentLevel,
|
||||
leaderboardEntries:
|
||||
leaderboardEntries.length > 0
|
||||
? leaderboardEntries
|
||||
: currentRun.currentLevel.leaderboardEntries,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user