This commit is contained in:
2026-05-01 01:30:02 +08:00
parent aabad6407f
commit 2e9d0f4640
92 changed files with 4548 additions and 248 deletions

View File

@@ -6,8 +6,11 @@ import {
applyLocalPuzzleFreezeTime,
advanceLocalPuzzleLevel,
dragLocalPuzzlePiece,
extendLocalPuzzleTime,
isLocalPuzzleRun,
refreshLocalPuzzleTimer,
restartLocalPuzzleLevel,
resolvePuzzleRestartLevelId,
setLocalPuzzlePaused,
startLocalPuzzleRun,
submitLocalPuzzleLeaderboard,
@@ -84,6 +87,50 @@ function solveCurrentLevel(run: ReturnType<typeof startLocalPuzzleRun>) {
}
describe('puzzleLocalRuntime', () => {
test('本地关卡切割和倒计时按正式配置推进并循环', () => {
let run = startLocalPuzzleRun(baseWork);
const actual = [run];
for (let index = 0; index < 15; index += 1) {
run = advanceLocalPuzzleLevel({
...run,
clearedLevelCount: run.clearedLevelCount + 1,
currentLevel: run.currentLevel
? {
...run.currentLevel,
status: 'cleared',
clearedAtMs: Date.now(),
elapsedMs: 1_000,
}
: null,
});
actual.push(run);
}
expect(
actual.map((item) => [
item.currentLevel?.gridSize,
item.currentLevel?.timeLimitMs,
]),
).toEqual([
[3, 300_000],
[4, 300_000],
[5, 300_000],
[5, 210_000],
[5, 210_000],
[6, 240_000],
[5, 210_000],
[7, 270_000],
[5, 240_000],
[7, 270_000],
[5, 210_000],
[6, 240_000],
[5, 210_000],
[7, 270_000],
[5, 240_000],
[7, 270_000],
]);
});
test('每次启动都会生成不同的初始打乱样式', async () => {
const firstRun = startLocalPuzzleRun(baseWork);
await new Promise((resolve) => setTimeout(resolve, 2));
@@ -297,6 +344,8 @@ describe('puzzleLocalRuntime', () => {
expect(clearedRun.currentLevel?.status).toBe('cleared');
expect(clearedRun.recommendedNextProfileId).toBeNull();
expect(clearedRun.nextLevelMode).toBe('none');
expect(clearedRun.recommendedNextWorks).toEqual([]);
expect(clearedRun.currentLevel?.elapsedMs).toBeGreaterThan(0);
expect(clearedRun.currentLevel?.leaderboardEntries).toEqual([]);
expect(clearedRun.leaderboardEntries).toEqual([]);
@@ -310,6 +359,8 @@ describe('puzzleLocalRuntime', () => {
expect(nextRun.currentLevel?.elapsedMs).toBeNull();
expect(nextRun.currentLevel?.leaderboardEntries).toEqual([]);
expect(nextRun.recommendedNextProfileId).toBeNull();
expect(nextRun.nextLevelMode).toBe('none');
expect(nextRun.recommendedNextWorks).toEqual([]);
});
test('连续推进下一关会重新打乱棋盘', () => {
@@ -376,6 +427,113 @@ describe('puzzleLocalRuntime', () => {
expect(nextRun).toBe(timedRun);
});
test('本地失败关卡可以续时一分钟', () => {
const run = startLocalPuzzleRun(baseWork);
const failedRun = refreshLocalPuzzleTimer({
...run,
currentLevel: run.currentLevel
? {
...run.currentLevel,
startedAtMs: Date.now() - run.currentLevel.timeLimitMs - 1_000,
}
: null,
});
const extendedRun = extendLocalPuzzleTime(failedRun);
expect(extendedRun.currentLevel?.status).toBe('playing');
expect(extendedRun.currentLevel?.remainingMs).toBe(60_000);
expect(extendedRun.currentLevel?.elapsedMs).toBeNull();
expect(extendedRun.currentLevel?.pauseStartedAtMs).toBeNull();
expect(extendedRun.currentLevel?.freezeUntilMs).toBeNull();
});
test('本地失败关卡重新开始会保留关卡索引并重建棋盘', () => {
const run = startLocalPuzzleRun(baseWork);
const failedRun = refreshLocalPuzzleTimer({
...run,
currentLevel: run.currentLevel
? {
...run.currentLevel,
startedAtMs: Date.now() - run.currentLevel.timeLimitMs - 1_000,
}
: null,
});
const restartedRun = restartLocalPuzzleLevel(failedRun);
expect(restartedRun.runId).not.toBe(failedRun.runId);
expect(restartedRun.currentLevel?.status).toBe('playing');
expect(restartedRun.currentLevel?.levelIndex).toBe(
failedRun.currentLevel?.levelIndex,
);
expect(restartedRun.currentLevel?.remainingMs).toBe(
restartedRun.currentLevel?.timeLimitMs,
);
expect(boardPositionSignature(restartedRun)).not.toBe(
boardPositionSignature(failedRun),
);
});
test('失败重开优先使用当前关卡 id旧快照缺失时按关卡序号兜底', () => {
const workWithLevels: PuzzleWorkSummary = {
...baseWork,
levels: [
{
levelId: 'puzzle-level-1',
levelName: '第一关',
pictureDescription: '第一关画面',
candidates: [],
selectedCandidateId: null,
coverImageSrc: '/level-1.png',
coverAssetId: null,
generationStatus: 'ready',
},
{
levelId: 'puzzle-level-2',
levelName: '第二关',
pictureDescription: '第二关画面',
candidates: [],
selectedCandidateId: null,
coverImageSrc: '/level-2.png',
coverAssetId: null,
generationStatus: 'ready',
},
],
};
const run = startLocalPuzzleRun(workWithLevels);
const secondLevelRun = {
...run,
currentLevelIndex: 2,
currentLevel: run.currentLevel
? {
...run.currentLevel,
levelIndex: 2,
levelId: null,
status: 'failed' as const,
}
: null,
};
expect(resolvePuzzleRestartLevelId(secondLevelRun, workWithLevels)).toBe(
'puzzle-level-2',
);
expect(
resolvePuzzleRestartLevelId(
{
...secondLevelRun,
currentLevel: secondLevelRun.currentLevel
? {
...secondLevelRun.currentLevel,
levelId: 'explicit-level',
}
: null,
},
workWithLevels,
),
).toBe('explicit-level');
});
test('暂停和冻结时间不会消耗本地倒计时', () => {
const run = startLocalPuzzleRun(baseWork);
const pausedRun = setLocalPuzzlePaused(

View File

@@ -15,17 +15,70 @@ 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,
let localPuzzleRunSequence = 0;
type PuzzleLevelConfig = {
gridSize: PuzzleGridSize;
timeLimitMs: number;
};
// 中文注释:本地兜底必须和后端按同一关卡序号解析切割规格与倒计时。
function resolvePuzzleLevelConfig(levelIndex: number): PuzzleLevelConfig {
const normalizedLevelIndex = Math.max(1, Math.floor(levelIndex || 1));
switch (normalizedLevelIndex) {
case 1:
return { gridSize: 3, timeLimitMs: 300_000 };
case 2:
return { gridSize: 4, timeLimitMs: 300_000 };
case 3:
return { gridSize: 5, timeLimitMs: 300_000 };
case 4:
return { gridSize: 5, timeLimitMs: 210_000 };
default: {
const loopIndex = ((Math.max(5, normalizedLevelIndex) - 5) % 6) + 5;
switch (loopIndex) {
case 5:
return { gridSize: 5, timeLimitMs: 210_000 };
case 6:
return { gridSize: 6, timeLimitMs: 240_000 };
case 7:
return { gridSize: 5, timeLimitMs: 210_000 };
case 8:
return { gridSize: 7, timeLimitMs: 270_000 };
case 9:
return { gridSize: 5, timeLimitMs: 240_000 };
default:
return { gridSize: 7, timeLimitMs: 270_000 };
}
}
}
}
function resolvePuzzleGridSize(clearedLevelCount: number): PuzzleGridSize {
return clearedLevelCount >= 3 ? 4 : 3;
return resolvePuzzleLevelConfig(clearedLevelCount + 1).gridSize;
}
const PUZZLE_INITIAL_SHUFFLE_ATTEMPTS = 64;
function buildLocalPuzzleRunId(profileId: string) {
localPuzzleRunSequence = (localPuzzleRunSequence + 1) % 1_000_000;
return `${LOCAL_PUZZLE_RUN_ID_PREFIX}${profileId}-${Date.now()}-${localPuzzleRunSequence}`;
}
export function resolvePuzzleRestartLevelId(
run: PuzzleRunSnapshot,
work: PuzzleWorkSummary | null | undefined,
): string | null {
const currentLevel = run.currentLevel;
if (!currentLevel) {
return null;
}
return (
currentLevel.levelId ??
work?.levels?.[Math.max(0, currentLevel.levelIndex - 1)]?.levelId ??
null
);
}
function buildShuffleSeed(...parts: Array<string | number>) {
let hash = 0x811c9dc5;
for (const part of parts.join('|')) {
@@ -77,7 +130,11 @@ function buildInitialPositions(gridSize: PuzzleGridSize, seed: number) {
row: Math.floor(index / gridSize),
col: index % gridSize,
}));
for (let attempt = 0; attempt < PUZZLE_INITIAL_SHUFFLE_ATTEMPTS; attempt += 1) {
for (
let attempt = 0;
attempt < PUZZLE_INITIAL_SHUFFLE_ATTEMPTS;
attempt += 1
) {
const shuffled = shufflePositions(
positions,
(seed + Math.imul(attempt, 2654435761)) >>> 0,
@@ -88,7 +145,11 @@ function buildInitialPositions(gridSize: PuzzleGridSize, seed: number) {
return shuffled;
}
}
return buildOriginalNeighborFreePositions(gridSize, seed) ?? positions;
return (
buildDeterministicNeighborFreePositions(gridSize, seed) ??
buildOriginalNeighborFreePositions(gridSize, seed) ??
positions
);
}
function boardCellKey(row: number, col: number) {
@@ -99,8 +160,8 @@ function clampElapsedMs(value: number) {
return Math.max(1_000, Math.round(value));
}
function resolvePuzzleLevelTimeLimitMs(gridSize: PuzzleGridSize) {
return PUZZLE_LEVEL_TIME_LIMIT_MS_BY_GRID_SIZE[gridSize];
function resolvePuzzleLevelTimeLimitMs(levelIndex: number) {
return resolvePuzzleLevelConfig(levelIndex || 1).timeLimitMs;
}
function resolveActiveFreezeElapsedMs(
@@ -110,7 +171,10 @@ function resolveActiveFreezeElapsedMs(
if (!level.freezeStartedAtMs || !level.freezeUntilMs) {
return 0;
}
return Math.max(0, Math.min(nowMs, level.freezeUntilMs) - level.freezeStartedAtMs);
return Math.max(
0,
Math.min(nowMs, level.freezeUntilMs) - level.freezeStartedAtMs,
);
}
function resolveEffectiveElapsedMs(
@@ -135,7 +199,11 @@ function settleExpiredFreeze(
level: PuzzleRuntimeLevelSnapshot,
nowMs: number,
): PuzzleRuntimeLevelSnapshot {
if (!level.freezeStartedAtMs || !level.freezeUntilMs || nowMs < level.freezeUntilMs) {
if (
!level.freezeStartedAtMs ||
!level.freezeUntilMs ||
nowMs < level.freezeUntilMs
) {
return level;
}
return {
@@ -168,8 +236,8 @@ function withResolvedTimer(run: PuzzleRunSnapshot, nowMs = Date.now()) {
};
}
function buildLevelTimerFields(gridSize: PuzzleGridSize) {
const timeLimitMs = resolvePuzzleLevelTimeLimitMs(gridSize);
function buildLevelTimerFields(levelIndex: number) {
const timeLimitMs = resolvePuzzleLevelTimeLimitMs(levelIndex);
return {
timeLimitMs,
remainingMs: timeLimitMs,
@@ -228,18 +296,6 @@ function buildPiecesFromPositions(
}));
}
function hasAnyCorrectNeighborPair(pieces: PuzzlePieceState[]) {
const piecesByCell = new Map(
pieces.map((piece) => [boardCellKey(piece.currentRow, piece.currentCol), piece]),
);
return pieces.some((piece) =>
neighborCells(piece.currentRow, piece.currentCol).some((neighbor) => {
const neighborPiece = piecesByCell.get(boardCellKey(neighbor.row, neighbor.col));
return Boolean(neighborPiece && areCorrectNeighbors(piece, neighborPiece));
}),
);
}
function areOriginalNeighbors(left: PuzzlePieceState, right: PuzzlePieceState) {
return (
Math.abs(right.correctRow - left.correctRow) +
@@ -250,12 +306,19 @@ function areOriginalNeighbors(left: PuzzlePieceState, right: PuzzlePieceState) {
function hasAnyOriginalNeighborPair(pieces: PuzzlePieceState[]) {
const piecesByCell = new Map(
pieces.map((piece) => [boardCellKey(piece.currentRow, piece.currentCol), piece]),
pieces.map((piece) => [
boardCellKey(piece.currentRow, piece.currentCol),
piece,
]),
);
return pieces.some((piece) =>
neighborCells(piece.currentRow, piece.currentCol).some((neighbor) => {
const neighborPiece = piecesByCell.get(boardCellKey(neighbor.row, neighbor.col));
return Boolean(neighborPiece && areOriginalNeighbors(piece, neighborPiece));
const neighborPiece = piecesByCell.get(
boardCellKey(neighbor.row, neighbor.col),
);
return Boolean(
neighborPiece && areOriginalNeighbors(piece, neighborPiece),
);
}),
);
}
@@ -269,6 +332,127 @@ function seededOrderKey(seed: number, value: number) {
return (state ^ (state >>> 16)) >>> 0;
}
function buildDeterministicNeighborFreePositions(
gridSize: PuzzleGridSize,
seed: number,
) {
if (gridSize === 3) {
return buildSeeded3x3NeighborFreePositions(seed);
}
if (gridSize === 4 || gridSize === 6) {
return buildAffineNeighborFreePositions(gridSize, 1, 1, 2, 1, seed);
}
if (gridSize === 5 || gridSize === 7) {
return buildAffineNeighborFreePositions(
gridSize,
0,
1,
2,
gridSize - 1,
seed,
);
}
return null;
}
function buildSeeded3x3NeighborFreePositions(seed: number) {
const layouts: Array<Array<[number, number]>> = [
[
[0, 1],
[1, 0],
[1, 2],
[2, 0],
[0, 2],
[2, 1],
[1, 1],
[2, 2],
[0, 0],
],
[
[0, 1],
[1, 0],
[1, 2],
[2, 0],
[0, 2],
[2, 1],
[2, 2],
[1, 1],
[0, 0],
],
[
[0, 1],
[1, 0],
[1, 2],
[2, 0],
[2, 2],
[0, 0],
[1, 1],
[0, 2],
[2, 1],
],
[
[0, 1],
[1, 0],
[1, 2],
[2, 1],
[0, 2],
[2, 0],
[0, 0],
[2, 2],
[1, 1],
],
[
[0, 1],
[1, 0],
[1, 2],
[2, 2],
[0, 2],
[2, 1],
[1, 1],
[2, 0],
[0, 0],
],
[
[0, 1],
[1, 0],
[2, 1],
[2, 0],
[2, 2],
[0, 2],
[1, 2],
[0, 0],
[1, 1],
],
];
const layout = layouts[Math.abs(seed) % layouts.length] ?? layouts[0];
return (
layout?.map(([row, col]) => ({
row,
col,
})) ?? null
);
}
function buildAffineNeighborFreePositions(
gridSize: PuzzleGridSize,
rowFromRow: number,
rowFromCol: number,
colFromRow: number,
colFromCol: number,
seed: number,
) {
const rowOffset = seed % gridSize;
const colOffset = Math.floor(seed / gridSize) % gridSize;
return Array.from({ length: gridSize * gridSize }, (_, index) => {
const row = Math.floor(index / gridSize);
const col = index % gridSize;
return {
row: (rowFromRow * row + rowFromCol * col + rowOffset) % gridSize,
col: (colFromRow * row + colFromCol * col + colOffset) % gridSize,
};
});
}
function buildOriginalNeighborFreePositions(
gridSize: PuzzleGridSize,
seed: number,
@@ -343,11 +527,14 @@ function violatesOriginalNeighborFreeRule(
return false;
}
const originalNeighbors =
Math.abs(Math.floor(pieceIndex / gridSize) - Math.floor(placedIndex / gridSize)) +
Math.abs(
Math.floor(pieceIndex / gridSize) - Math.floor(placedIndex / gridSize),
) +
Math.abs((pieceIndex % gridSize) - (placedIndex % gridSize)) ===
1;
const currentNeighbors =
Math.abs(cell.row - placedCell.row) + Math.abs(cell.col - placedCell.col) ===
Math.abs(cell.row - placedCell.row) +
Math.abs(cell.col - placedCell.col) ===
1;
return originalNeighbors && currentNeighbors;
});
@@ -357,7 +544,10 @@ function resolveMergedGroups(
pieces: PuzzlePieceState[],
): PuzzleMergedGroupState[] {
const piecesByCell = new Map(
pieces.map((piece) => [boardCellKey(piece.currentRow, piece.currentCol), piece]),
pieces.map((piece) => [
boardCellKey(piece.currentRow, piece.currentCol),
piece,
]),
);
const piecesById = new Map(pieces.map((piece) => [piece.pieceId, piece]));
const visited = new Set<string>();
@@ -386,7 +576,9 @@ function resolveMergedGroups(
currentPiece.currentRow,
currentPiece.currentCol,
)) {
const neighborPiece = piecesByCell.get(boardCellKey(neighbor.row, neighbor.col));
const neighborPiece = piecesByCell.get(
boardCellKey(neighbor.row, neighbor.col),
);
if (neighborPiece && areCorrectNeighbors(currentPiece, neighborPiece)) {
queue.push(neighborPiece.pieceId);
}
@@ -433,7 +625,8 @@ function rebuildBoardSnapshot(
piece.currentCol === piece.correctCol,
);
const allPiecesMergedIntoOneGroup = mergedGroups.some(
(group) => group.pieceIds.length === nextPieces.length && nextPieces.length > 1,
(group) =>
group.pieceIds.length === nextPieces.length && nextPieces.length > 1,
);
const allTilesResolved =
allPiecesInCorrectCells || allPiecesMergedIntoOneGroup;
@@ -560,12 +753,17 @@ function buildFallbackLocalLevel(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
gridSize,
profileId: nextProfileId,
levelName: buildLocalLevelName(currentLevel.levelName, nextLevelIndex),
board: buildInitialBoard(gridSize, run.runId, nextProfileId, nextLevelIndex),
board: buildInitialBoard(
gridSize,
run.runId,
nextProfileId,
nextLevelIndex,
),
status: 'playing',
startedAtMs,
clearedAtMs: null,
elapsedMs: null,
...buildLevelTimerFields(gridSize),
...buildLevelTimerFields(nextLevelIndex),
leaderboardEntries: [],
},
recommendedNextProfileId: null,
@@ -577,9 +775,11 @@ function buildFallbackLocalLevel(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
};
}
export function startLocalPuzzleRun(item: PuzzleWorkSummary): PuzzleRunSnapshot {
export function startLocalPuzzleRun(
item: PuzzleWorkSummary,
): PuzzleRunSnapshot {
const gridSize = resolvePuzzleGridSize(0);
const runId = `${LOCAL_PUZZLE_RUN_ID_PREFIX}${item.profileId}-${Date.now()}`;
const runId = buildLocalPuzzleRunId(item.profileId);
const startedAtMs = Date.now();
const firstLevel = item.levels?.[0] ?? null;
const firstLevelName = firstLevel?.levelName || item.levelName;
@@ -608,7 +808,7 @@ export function startLocalPuzzleRun(item: PuzzleWorkSummary): PuzzleRunSnapshot
startedAtMs,
clearedAtMs: null,
elapsedMs: null,
...buildLevelTimerFields(gridSize),
...buildLevelTimerFields(1),
leaderboardEntries: [],
},
recommendedNextProfileId: null,
@@ -631,7 +831,9 @@ export function swapLocalPuzzlePieces(
}
const pieces = currentLevel.board.pieces.map((piece) => ({ ...piece }));
const first = pieces.find((piece) => piece.pieceId === payload.firstPieceId);
const second = pieces.find((piece) => piece.pieceId === payload.secondPieceId);
const second = pieces.find(
(piece) => piece.pieceId === payload.secondPieceId,
);
if (!first || !second) {
return timedRun;
}
@@ -641,7 +843,10 @@ export function swapLocalPuzzlePieces(
second.currentRow = firstPosition.row;
second.currentCol = firstPosition.col;
return applyNextBoard(timedRun, rebuildBoardSnapshot(currentLevel.gridSize, pieces));
return applyNextBoard(
timedRun,
rebuildBoardSnapshot(currentLevel.gridSize, pieces),
);
}
function dragSinglePiece(
@@ -717,7 +922,8 @@ function dragGroup(
col: piece.currentCol,
}))
.filter(
(position) => !targetCellKeys.has(boardCellKey(position.row, position.col)),
(position) =>
!targetCellKeys.has(boardCellKey(position.row, position.col)),
)
.sort((left, right) => left.row - right.row || left.col - right.col);
const occupyingPieces = targetPositions
@@ -733,7 +939,8 @@ function dragGroup(
.filter((piece): piece is PuzzlePieceState => Boolean(piece))
.sort(
(left, right) =>
left.currentRow - right.currentRow || left.currentCol - right.currentCol,
left.currentRow - right.currentRow ||
left.currentCol - right.currentCol,
);
if (occupyingPieces.length !== vacatedPositions.length) {
@@ -796,20 +1003,27 @@ export function dragLocalPuzzlePiece(
dragSinglePiece(pieces, moving, payload.targetRow, payload.targetCol);
}
return applyNextBoard(timedRun, rebuildBoardSnapshot(currentLevel.gridSize, pieces));
return applyNextBoard(
timedRun,
rebuildBoardSnapshot(currentLevel.gridSize, pieces),
);
}
export function advanceLocalPuzzleLevel(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
export function advanceLocalPuzzleLevel(
run: PuzzleRunSnapshot,
): PuzzleRunSnapshot {
return buildFallbackLocalLevel(run);
}
export function restartLocalPuzzleLevel(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
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 runId = buildLocalPuzzleRunId(currentLevel.profileId);
const startedAtMs = Date.now();
return {
...run,
@@ -828,7 +1042,7 @@ export function restartLocalPuzzleLevel(run: PuzzleRunSnapshot): PuzzleRunSnapsh
startedAtMs,
clearedAtMs: null,
elapsedMs: null,
...buildLevelTimerFields(currentLevel.gridSize),
...buildLevelTimerFields(currentLevel.levelIndex),
leaderboardEntries: [],
},
};
@@ -876,7 +1090,9 @@ export function submitLocalPuzzleLeaderboard(
};
}
export function refreshLocalPuzzleTimer(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
export function refreshLocalPuzzleTimer(
run: PuzzleRunSnapshot,
): PuzzleRunSnapshot {
return withResolvedTimer(run);
}
@@ -928,7 +1144,9 @@ export function applyLocalPuzzleFreezeTime(
};
}
export function extendLocalPuzzleTime(run: PuzzleRunSnapshot): PuzzleRunSnapshot {
export function extendLocalPuzzleTime(
run: PuzzleRunSnapshot,
): PuzzleRunSnapshot {
const timedRun = withResolvedTimer(run);
const currentLevel = timedRun.currentLevel;
if (!currentLevel || currentLevel.status !== 'failed') {

View File

@@ -1,6 +1,7 @@
export {
getPuzzleWorkDetail,
claimPuzzleWorkPointIncentive,
deletePuzzleWork,
getPuzzleWorkDetail,
listPuzzleWorks,
puzzleWorksClient,
updatePuzzleWork,

View File

@@ -98,7 +98,24 @@ export async function deletePuzzleWork(profileId: string) {
);
}
/**
* 领取当前用户名下拼图作品的整数陶泥币激励。
*/
export async function claimPuzzleWorkPointIncentive(profileId: string) {
return requestJson<PuzzleWorkMutationResponse>(
`${PUZZLE_WORKS_API_BASE}/${encodeURIComponent(profileId)}/point-incentive/claim`,
{
method: 'POST',
},
'领取拼图积分激励失败',
{
retry: PUZZLE_WORKS_WRITE_RETRY,
},
);
}
export const puzzleWorksClient = {
claimPointIncentive: claimPuzzleWorkPointIncentive,
delete: deletePuzzleWork,
getDetail: getPuzzleWorkDetail,
list: listPuzzleWorks,