import type { Match3DClickConfirmation, Match3DClickItemRequest, Match3DClickItemResult, Match3DClickRejectReason, Match3DClickResponse, Match3DRunResponse, StopMatch3DRunRequest, } from '../../../packages/shared/src/contracts/match3dRuntime'; import { type ApiRequestOptions, type ApiRetryOptions, requestJson, } from '../apiClient'; const MATCH3D_RUNTIME_READ_RETRY: ApiRetryOptions = { maxRetries: 1, baseDelayMs: 120, maxDelayMs: 360, }; const MATCH3D_RUNTIME_WRITE_RETRY: ApiRetryOptions = { maxRetries: 1, baseDelayMs: 120, maxDelayMs: 360, retryUnsafeMethods: true, }; type Match3DRuntimeRequestOptions = Pick< ApiRequestOptions, 'skipRefresh' | 'notifyAuthStateChange' | 'clearAuthOnUnauthorized' >; function normalizeRejectStatus(reason?: Match3DClickRejectReason | null) { switch (reason) { case 'snapshot_version_mismatch': return 'VersionConflict'; case 'tray_full': return 'RejectedTrayFull'; case 'run_not_active': return 'RunFinished'; case 'item_not_found': case 'item_not_in_board': return 'RejectedAlreadyMoved'; case 'item_not_clickable': default: return 'RejectedNotClickable'; } } function mapClickConfirmation( request: Match3DClickItemRequest, confirmation: Match3DClickConfirmation, ): Match3DClickItemResult { return { status: confirmation.accepted ? 'Accepted' : normalizeRejectStatus(confirmation.rejectReason), run: confirmation.run, acceptedItemInstanceId: confirmation.accepted ? request.itemInstanceId : undefined, clearedItemInstanceIds: confirmation.clearedItemInstanceIds, failureReason: confirmation.run.failureReason, }; } /** * 基于作品启动一局抓大鹅正式 run。 */ export function startMatch3DRun( profileId: string, options: Match3DRuntimeRequestOptions = {}, ) { return requestJson( `/api/runtime/match3d/works/${encodeURIComponent(profileId)}/runs`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ profileId }), }, '启动抓大鹅玩法失败', { retry: MATCH3D_RUNTIME_WRITE_RETRY, skipRefresh: options.skipRefresh, notifyAuthStateChange: options.notifyAuthStateChange, clearAuthOnUnauthorized: options.clearAuthOnUnauthorized, }, ); } /** * 读取抓大鹅运行态快照。 */ export function getMatch3DRun(runId: string) { return requestJson( `/api/runtime/match3d/runs/${encodeURIComponent(runId)}`, { method: 'GET' }, '读取抓大鹅运行快照失败', { retry: MATCH3D_RUNTIME_READ_RETRY }, ); } /** * 提交一次点击,由后端做权威确认;返回值适配运行壳已实现的即时反馈语义。 */ export async function clickMatch3DItem( runId: string, payload: Match3DClickItemRequest, ) { const response = await requestJson( `/api/runtime/match3d/runs/${encodeURIComponent(runId)}/click`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...payload, runId: payload.runId ?? runId, }), }, '确认抓大鹅点击失败', { retry: MATCH3D_RUNTIME_WRITE_RETRY }, ); return mapClickConfirmation(payload, response.confirmation); } /** * 停止当前抓大鹅运行态。 */ export function stopMatch3DRun( runId: string, payload: StopMatch3DRunRequest = { clientActionId: `match3d-stop-${Date.now()}`, }, ) { return requestJson( `/api/runtime/match3d/runs/${encodeURIComponent(runId)}/stop`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, '停止抓大鹅玩法失败', { retry: MATCH3D_RUNTIME_WRITE_RETRY }, ); } /** * 基于当前 run 重开一局。 */ export function restartMatch3DRun(runId: string) { return requestJson( `/api/runtime/match3d/runs/${encodeURIComponent(runId)}/restart`, { method: 'POST' }, '重新开始抓大鹅玩法失败', { retry: MATCH3D_RUNTIME_WRITE_RETRY }, ); } /** * 前端倒计时归零后通知后端确认失败状态。 */ export function finishMatch3DTimeUp(runId: string) { return requestJson( `/api/runtime/match3d/runs/${encodeURIComponent(runId)}/time-up`, { method: 'POST' }, '同步抓大鹅倒计时失败', { retry: MATCH3D_RUNTIME_WRITE_RETRY }, ); } export const match3dRuntimeClient = { clickItem: clickMatch3DItem, finishTimeUp: finishMatch3DTimeUp, getRun: getMatch3DRun, restartRun: restartMatch3DRun, startRun: startMatch3DRun, stopRun: stopMatch3DRun, };