feat: scaffold mvp shell and content runtime

This commit is contained in:
manpengan
2026-03-29 00:36:28 +08:00
parent 25a38cbf05
commit c118e24bd1
25 changed files with 2903 additions and 7 deletions

View File

@@ -0,0 +1,24 @@
export function createBundleResolver(cityRegistry) {
return {
resolveCityBundle(cityId) {
const city = cityRegistry.getCity(cityId)
return city ? city.bundle : null
},
ensureCityBundle(cityId) {
const bundle = this.resolveCityBundle(cityId)
if (!bundle) {
return null
}
return {
cityId,
...bundle,
status: 'ready',
}
},
}
}
export default createBundleResolver

View File

@@ -0,0 +1,30 @@
export function createCityRegistry(cities) {
const cityMap = new Map(
[...cities]
.sort((left, right) => left.sortOrder - right.sortOrder)
.map((city) => [city.id, city]),
)
return {
getAllCities() {
return [...cityMap.values()]
},
getCity(cityId) {
return cityMap.get(cityId) ?? null
},
listCitiesByContinent(continentId) {
return [...cityMap.values()].filter((city) => city.continentId === continentId)
},
getLevelPreset(cityId, levelId) {
const city = cityMap.get(cityId)
if (!city) {
return null
}
return city.levelPresets.find((preset) => preset.id === levelId) ?? null
},
}
}
export default createCityRegistry

View File

@@ -0,0 +1,21 @@
export function createContinentRegistry(continents) {
const continentMap = new Map(
[...continents]
.sort((left, right) => left.sortOrder - right.sortOrder)
.map((continent) => [continent.id, continent]),
)
return {
getContinentList() {
return [...continentMap.values()]
},
getContinent(continentId) {
return continentMap.get(continentId) ?? null
},
hasContinent(continentId) {
return continentMap.has(continentId)
},
}
}
export default createContinentRegistry

View File

@@ -0,0 +1,45 @@
function getLevelEntries(levelProgress = {}) {
return Object.values(levelProgress)
}
export function projectCityProgress(city, playerState) {
const levelProgress = playerState.levelProgress[city.id] ?? {}
const levels = getLevelEntries(levelProgress)
const completedLevels = levels.filter((level) => level.completed).length
const totalStars = levels.reduce((sum, level) => sum + (level.stars ?? 0), 0)
const totalLevels = city.levelPresets.length
const isUnlocked = playerState.unlockedCities.includes(city.id)
const isCompleted = totalLevels > 0 && completedLevels >= totalLevels
return {
cityId: city.id,
completedLevels,
totalLevels,
totalStars,
isUnlocked,
isCompleted,
isCollected: playerState.collectedCats.includes(city.id),
hasPassportStamp: playerState.passportStamps.includes(city.id),
}
}
export function projectCityCardView(city, playerState) {
const progress = projectCityProgress(city, playerState)
return {
cityId: city.id,
name: city.display.name,
nameEn: city.display.nameEn,
bgColor: city.display.bgColor,
catImage: city.cover.catImage,
catThumb: city.cover.catThumb,
isUnlocked: progress.isUnlocked,
isCompleted: progress.isCompleted,
isCollected: progress.isCollected,
completedLevels: progress.completedLevels,
totalLevels: progress.totalLevels,
totalStars: progress.totalStars,
}
}
export default projectCityCardView