feat: add puzzle clear template runtime

This commit is contained in:
2026-06-03 22:11:46 +08:00
parent 6e74cf5add
commit 1b5e098225
148 changed files with 19588 additions and 241 deletions

View File

@@ -163,6 +163,7 @@ mod tests {
let row = BarkBattleGalleryViewRow {
work_id: "BB-33333333".to_string(),
owner_user_id: "user-3".to_string(),
author_display_name: "声浪玩家".to_string(),
source_draft_id: Some("bark-battle-draft-3".to_string()),
config_version: 1,
ruleset_version: "bark-battle-ruleset-v1".to_string(),

View File

@@ -0,0 +1,289 @@
use super::*;
pub use shared_contracts::puzzle_clear::{
PuzzleClearActionRequest, PuzzleClearActionResponse, PuzzleClearActionType,
PuzzleClearBoardCell, PuzzleClearBoardSnapshot, PuzzleClearCardAsset, PuzzleClearDraftResponse,
PuzzleClearGenerationStatus, PuzzleClearImageAsset, PuzzleClearNextLevelRequest,
PuzzleClearPatternGroup, PuzzleClearRetryLevelRequest, PuzzleClearRunResponse,
PuzzleClearRunStatus, PuzzleClearRuntimeSnapshotResponse, PuzzleClearSessionResponse,
PuzzleClearSessionSnapshotResponse, PuzzleClearStartRunRequest, PuzzleClearSwapRequest,
PuzzleClearTimeUpRequest, PuzzleClearWorkDetailResponse, PuzzleClearWorkMutationResponse,
PuzzleClearWorkProfileResponse, PuzzleClearWorkSummaryResponse, PuzzleClearWorksResponse,
PuzzleClearWorkspaceCreateRequest,
};
pub(crate) fn map_puzzle_clear_agent_session_procedure_result(
result: PuzzleClearAgentSessionProcedureResult,
) -> Result<PuzzleClearSessionSnapshotResponse, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let session = result
.session
.ok_or_else(|| SpacetimeClientError::missing_snapshot("puzzle clear agent session 快照"))?;
Ok(map_puzzle_clear_session_snapshot(session))
}
pub(crate) fn map_puzzle_clear_work_procedure_result(
result: PuzzleClearWorkProcedureResult,
) -> Result<PuzzleClearWorkProfileResponse, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let work = result
.work
.ok_or_else(|| SpacetimeClientError::missing_snapshot("puzzle clear work 快照"))?;
Ok(map_puzzle_clear_work_snapshot(work))
}
pub(crate) fn map_puzzle_clear_works_procedure_result(
result: PuzzleClearWorksProcedureResult,
) -> Result<Vec<PuzzleClearWorkProfileResponse>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(result
.items
.into_iter()
.map(map_puzzle_clear_work_snapshot)
.collect())
}
pub(crate) fn map_puzzle_clear_run_procedure_result(
result: PuzzleClearRunProcedureResult,
) -> Result<PuzzleClearRuntimeSnapshotResponse, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let run = result
.run
.ok_or_else(|| SpacetimeClientError::missing_snapshot("puzzle clear run 快照"))?;
Ok(map_puzzle_clear_run_snapshot(run))
}
pub(crate) fn map_puzzle_clear_gallery_card_view_row(
row: PuzzleClearGalleryCardViewRow,
) -> PuzzleClearWorkSummaryResponse {
PuzzleClearWorkSummaryResponse {
runtime_kind: "puzzle-clear".to_string(),
work_id: row.work_id,
profile_id: row.profile_id,
owner_user_id: row.owner_user_id,
source_session_id: None,
work_title: row.work_title,
work_description: row.work_description,
theme_prompt: row.theme_prompt,
cover_image_src: row.cover_image_src,
publication_status: normalize_publication_status(&row.publication_status).to_string(),
play_count: row.play_count,
updated_at: format_timestamp_micros(row.updated_at_micros),
published_at: row.published_at_micros.map(format_timestamp_micros),
publish_ready: true,
generation_status: parse_generation_status(&row.generation_status),
}
}
fn map_puzzle_clear_session_snapshot(
snapshot: PuzzleClearAgentSessionSnapshot,
) -> PuzzleClearSessionSnapshotResponse {
PuzzleClearSessionSnapshotResponse {
session_id: snapshot.session_id,
owner_user_id: snapshot.owner_user_id,
status: parse_generation_status(&snapshot.status),
draft: snapshot.draft.map(map_puzzle_clear_draft_snapshot),
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
fn map_puzzle_clear_work_snapshot(
snapshot: PuzzleClearWorkSnapshot,
) -> PuzzleClearWorkProfileResponse {
let atlas_asset = map_image_asset(snapshot.atlas_asset.clone());
let pattern_groups = snapshot
.pattern_groups
.clone()
.into_iter()
.map(map_pattern_group)
.collect::<Vec<_>>();
let card_assets = snapshot
.card_assets
.clone()
.into_iter()
.map(map_card_asset)
.collect::<Vec<_>>();
let board_background_asset = snapshot.board_background_asset.clone().map(map_image_asset);
let draft = PuzzleClearDraftResponse {
template_id: "puzzle-clear".to_string(),
template_name: "拼消消".to_string(),
profile_id: Some(snapshot.profile_id.clone()),
work_title: snapshot.work_title.clone(),
work_description: snapshot.work_description.clone(),
theme_prompt: snapshot.theme_prompt.clone(),
board_background_prompt: snapshot.board_background_prompt.clone(),
generate_board_background: snapshot.generate_board_background,
board_background_asset: board_background_asset.clone(),
card_back_image_src: snapshot.card_back_image_src.clone(),
atlas_asset: Some(atlas_asset.clone()),
pattern_groups: pattern_groups.clone(),
card_assets: card_assets.clone(),
generation_status: parse_generation_status(&snapshot.generation_status),
};
PuzzleClearWorkProfileResponse {
summary: PuzzleClearWorkSummaryResponse {
runtime_kind: "puzzle-clear".to_string(),
work_id: snapshot.work_id,
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
source_session_id: empty_string_to_none(snapshot.source_session_id),
work_title: snapshot.work_title,
work_description: snapshot.work_description,
theme_prompt: snapshot.theme_prompt,
cover_image_src: snapshot.cover_image_src,
publication_status: normalize_publication_status(&snapshot.publication_status)
.to_string(),
play_count: snapshot.play_count,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
publish_ready: snapshot.publish_ready,
generation_status: parse_generation_status(&snapshot.generation_status),
},
draft,
board_background_asset,
atlas_asset,
pattern_groups,
card_assets,
}
}
fn map_puzzle_clear_draft_snapshot(snapshot: PuzzleClearDraftSnapshot) -> PuzzleClearDraftResponse {
PuzzleClearDraftResponse {
template_id: snapshot.template_id,
template_name: snapshot.template_name,
profile_id: snapshot.profile_id,
work_title: snapshot.work_title,
work_description: snapshot.work_description,
theme_prompt: snapshot.theme_prompt,
board_background_prompt: snapshot.board_background_prompt,
generate_board_background: snapshot.generate_board_background,
board_background_asset: snapshot.board_background_asset.map(map_image_asset),
card_back_image_src: snapshot.card_back_image_src,
atlas_asset: snapshot.atlas_asset.map(map_image_asset),
pattern_groups: snapshot
.pattern_groups
.into_iter()
.map(map_pattern_group)
.collect(),
card_assets: snapshot
.card_assets
.into_iter()
.map(map_card_asset)
.collect(),
generation_status: parse_generation_status(&snapshot.generation_status),
}
}
fn map_image_asset(snapshot: PuzzleClearImageAssetSnapshot) -> PuzzleClearImageAsset {
PuzzleClearImageAsset {
asset_id: snapshot.asset_id,
image_src: snapshot.image_src,
image_object_key: snapshot.image_object_key,
asset_object_id: snapshot.asset_object_id,
generation_provider: snapshot.generation_provider,
prompt: snapshot.prompt,
width: snapshot.width,
height: snapshot.height,
}
}
fn map_pattern_group(snapshot: PuzzleClearPatternGroupSnapshot) -> PuzzleClearPatternGroup {
PuzzleClearPatternGroup {
group_id: snapshot.group_id,
shape: snapshot.shape,
width: snapshot.width,
height: snapshot.height,
atlas_x: snapshot.atlas_x,
atlas_y: snapshot.atlas_y,
atlas_width: snapshot.atlas_width,
atlas_height: snapshot.atlas_height,
}
}
fn map_card_asset(snapshot: PuzzleClearCardAssetSnapshot) -> PuzzleClearCardAsset {
PuzzleClearCardAsset {
card_id: snapshot.card_id,
group_id: snapshot.group_id,
shape: snapshot.shape,
orientation: snapshot.orientation,
part_x: snapshot.part_x,
part_y: snapshot.part_y,
image_src: snapshot.image_src,
image_object_key: snapshot.image_object_key,
asset_object_id: snapshot.asset_object_id,
source_atlas_cell: snapshot.source_atlas_cell,
}
}
fn map_puzzle_clear_run_snapshot(
snapshot: PuzzleClearRuntimeSnapshot,
) -> PuzzleClearRuntimeSnapshotResponse {
PuzzleClearRuntimeSnapshotResponse {
run_id: snapshot.run_id,
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
runtime_mode: None,
status: parse_run_status(&snapshot.status),
level_index: snapshot.level_index,
clears_done: snapshot.clears_done,
target_clears: snapshot.target_clears,
level_duration_seconds: snapshot.level_duration_seconds,
level_started_at_ms: snapshot.level_started_at_ms,
board: PuzzleClearBoardSnapshot {
rows: snapshot.board.rows,
cols: snapshot.board.cols,
cells: snapshot
.board
.cells
.into_iter()
.map(|cell| PuzzleClearBoardCell {
row: cell.row,
col: cell.col,
card: cell.card.map(map_card_asset),
locked_group_id: cell.locked_group_id,
})
.collect(),
},
ready_columns: snapshot
.ready_columns
.into_iter()
.map(|column| column.into_iter().map(map_card_asset).collect())
.collect(),
started_at_ms: snapshot.started_at_ms,
finished_at_ms: snapshot.finished_at_ms,
}
}
fn parse_generation_status(value: &str) -> PuzzleClearGenerationStatus {
match value {
"generating" => PuzzleClearGenerationStatus::Generating,
"ready" => PuzzleClearGenerationStatus::Ready,
"failed" => PuzzleClearGenerationStatus::Failed,
_ => PuzzleClearGenerationStatus::Draft,
}
}
fn parse_run_status(value: &str) -> PuzzleClearRunStatus {
match value {
"level_failed" => PuzzleClearRunStatus::LevelFailed,
"level_cleared" => PuzzleClearRunStatus::LevelCleared,
"finished" => PuzzleClearRunStatus::Finished,
_ => PuzzleClearRunStatus::Playing,
}
}
fn normalize_publication_status(value: &str) -> &str {
match value {
"Published" | "published" => "published",
_ => "draft",
}
}