This commit is contained in:
2026-05-01 00:33:39 +08:00
parent 61969c5116
commit fe02603ba1
68 changed files with 4586 additions and 748 deletions

View File

@@ -14,6 +14,7 @@ import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/p
const LOCAL_PUZZLE_RUN_ID_PREFIX = 'local-puzzle-run-';
const PUZZLE_FREEZE_TIME_DURATION_MS = 10_000;
const PUZZLE_EXTEND_TIME_DURATION_MS = 60_000;
const PUZZLE_LEVEL_TIME_LIMIT_MS_BY_GRID_SIZE: Record<PuzzleGridSize, number> = {
3: 180_000,
4: 300_000,
@@ -499,6 +500,10 @@ function applyNextBoard(
},
leaderboardEntries: justCleared ? [] : run.leaderboardEntries,
recommendedNextProfileId: run.recommendedNextProfileId,
nextLevelMode: run.nextLevelMode ?? 'none',
nextLevelProfileId: run.nextLevelProfileId ?? null,
nextLevelId: run.nextLevelId ?? null,
recommendedNextWorks: run.recommendedNextWorks ?? [],
};
}
@@ -551,6 +556,7 @@ function buildFallbackLocalLevel(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
...currentLevel,
runId: run.runId,
levelIndex: nextLevelIndex,
levelId: null,
gridSize,
profileId: nextProfileId,
levelName: buildLocalLevelName(currentLevel.levelName, nextLevelIndex),
@@ -563,6 +569,10 @@ function buildFallbackLocalLevel(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
leaderboardEntries: [],
},
recommendedNextProfileId: null,
nextLevelMode: 'none',
nextLevelProfileId: null,
nextLevelId: null,
recommendedNextWorks: [],
leaderboardEntries: [],
};
}
@@ -571,6 +581,10 @@ export function startLocalPuzzleRun(item: PuzzleWorkSummary): PuzzleRunSnapshot
const gridSize = resolvePuzzleGridSize(0);
const runId = `${LOCAL_PUZZLE_RUN_ID_PREFIX}${item.profileId}-${Date.now()}`;
const startedAtMs = Date.now();
const firstLevel = item.levels?.[0] ?? null;
const firstLevelName = firstLevel?.levelName || item.levelName;
const firstCoverImageSrc = firstLevel?.coverImageSrc ?? item.coverImageSrc;
const secondLevel = item.levels?.[1] ?? null;
return {
runId,
entryProfileId: item.profileId,
@@ -582,12 +596,13 @@ export function startLocalPuzzleRun(item: PuzzleWorkSummary): PuzzleRunSnapshot
currentLevel: {
runId,
levelIndex: 1,
levelId: firstLevel?.levelId ?? null,
gridSize,
profileId: item.profileId,
levelName: item.levelName,
levelName: firstLevelName,
authorDisplayName: item.authorDisplayName,
themeTags: item.themeTags,
coverImageSrc: item.coverImageSrc,
coverImageSrc: firstCoverImageSrc,
board: buildInitialBoard(gridSize, runId, item.profileId, 1),
status: 'playing',
startedAtMs,
@@ -597,6 +612,10 @@ export function startLocalPuzzleRun(item: PuzzleWorkSummary): PuzzleRunSnapshot
leaderboardEntries: [],
},
recommendedNextProfileId: null,
nextLevelMode: secondLevel ? 'sameWork' : 'none',
nextLevelProfileId: secondLevel ? item.profileId : null,
nextLevelId: secondLevel?.levelId ?? null,
recommendedNextWorks: [],
leaderboardEntries: [],
};
}
@@ -784,6 +803,37 @@ export function advanceLocalPuzzleLevel(run: PuzzleRunSnapshot): PuzzleRunSnapsh
return buildFallbackLocalLevel(run);
}
export function restartLocalPuzzleLevel(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
const currentLevel = run.currentLevel;
if (!currentLevel) {
return run;
}
const runId = `${LOCAL_PUZZLE_RUN_ID_PREFIX}${currentLevel.profileId}-${Date.now()}`;
const startedAtMs = Date.now();
return {
...run,
runId,
leaderboardEntries: [],
currentLevel: {
...currentLevel,
runId,
board: buildInitialBoard(
currentLevel.gridSize,
runId,
currentLevel.profileId,
currentLevel.levelIndex,
),
status: 'playing',
startedAtMs,
clearedAtMs: null,
elapsedMs: null,
...buildLevelTimerFields(currentLevel.gridSize),
leaderboardEntries: [],
},
};
}
/**
* 判断当前拼图运行态是否为前端本地兜底 run。
* 这类 run 没有后端持久化记录,不能再调用依赖真实 runId 的排行榜接口。
@@ -877,3 +927,34 @@ export function applyLocalPuzzleFreezeTime(
},
};
}
export function extendLocalPuzzleTime(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
const timedRun = withResolvedTimer(run);
const currentLevel = timedRun.currentLevel;
if (!currentLevel || currentLevel.status !== 'failed') {
return timedRun;
}
const nowMs = Date.now();
const consumedBeforeExtend = Math.max(
0,
currentLevel.timeLimitMs - PUZZLE_EXTEND_TIME_DURATION_MS,
);
return {
...timedRun,
currentLevel: {
...currentLevel,
status: 'playing',
startedAtMs: nowMs - consumedBeforeExtend,
clearedAtMs: null,
elapsedMs: null,
remainingMs: PUZZLE_EXTEND_TIME_DURATION_MS,
pausedAccumulatedMs: 0,
pauseStartedAtMs: null,
freezeAccumulatedMs: 0,
freezeStartedAtMs: null,
freezeUntilMs: null,
},
};
}