feat(adventure): add state management, tags, and prestige

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Brown
2026-03-26 17:32:14 -07:00
parent 5c342d439f
commit bd769f6fc6
3 changed files with 145 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
import type { CurrentRun, Layer, SaveState } from '@/types'
const STORAGE_KEY = 'codebase-caverns-v2'
const SAVE_VERSION = 1
function createFreshRun(layer: Layer): CurrentRun {
return {
layer,
path: [],
resolvedChallenges: {},
conceptTags: [],
insightEarned: 0,
currentRoom: 'entry'
}
}
function createDefaultSave(): SaveState {
return {
version: SAVE_VERSION,
currentRun: createFreshRun(1),
history: [],
persistent: {
totalInsight: 0,
currentLayer: 1,
achievements: []
}
}
}
function loadSave(): SaveState {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return createDefaultSave()
const parsed: unknown = JSON.parse(raw)
if (
typeof parsed === 'object' &&
parsed !== null &&
'version' in parsed &&
(parsed as SaveState).version === SAVE_VERSION
) {
return parsed as SaveState
}
return createDefaultSave()
} catch {
return createDefaultSave()
}
}
function persistSave(save: SaveState): void {
localStorage.setItem(STORAGE_KEY, JSON.stringify(save))
}
function clearSave(): void {
localStorage.removeItem(STORAGE_KEY)
}
function isV1Save(): boolean {
try {
const raw = localStorage.getItem('codebase-caverns')
return raw !== null
} catch {
return false
}
}
export {
clearSave,
createDefaultSave,
createFreshRun,
isV1Save,
loadSave,
persistSave,
SAVE_VERSION
}

View File

@@ -0,0 +1,36 @@
import type { Layer, RunRecord, SaveState } from '@/types'
import { createFreshRun } from '@/state/gameState'
function finalizeRun(save: SaveState, narrativeSummary: string): RunRecord {
return {
layer: save.currentRun.layer,
path: save.currentRun.path,
challenges: { ...save.currentRun.resolvedChallenges },
conceptTags: [...save.currentRun.conceptTags],
insightEarned: save.currentRun.insightEarned,
narrativeSummary
}
}
function canPrestige(save: SaveState): boolean {
return save.persistent.currentLayer < 3
}
function prestige(save: SaveState, narrativeSummary: string): SaveState {
const record = finalizeRun(save, narrativeSummary)
const nextLayer = Math.min(save.persistent.currentLayer + 1, 3) as Layer
return {
...save,
currentRun: createFreshRun(nextLayer),
history: [...save.history, record],
persistent: {
...save.persistent,
totalInsight:
save.persistent.totalInsight + save.currentRun.insightEarned,
currentLayer: nextLayer
}
}
}
export { canPrestige, finalizeRun, prestige }

View File

@@ -0,0 +1,34 @@
import type { RoomDefinition, SaveState } from '@/types'
function hasAllPrerequisites(
room: RoomDefinition,
earnedTags: string[]
): boolean {
return room.prerequisites.every((tag) => earnedTags.includes(tag))
}
function getUnmetPrerequisites(
room: RoomDefinition,
earnedTags: string[]
): string[] {
return room.prerequisites.filter((tag) => !earnedTags.includes(tag))
}
function canEnterRoom(room: RoomDefinition, save: SaveState): boolean {
return hasAllPrerequisites(room, save.currentRun.conceptTags)
}
function grantTags(save: SaveState, tags: string[]): SaveState {
const newTags = tags.filter((t) => !save.currentRun.conceptTags.includes(t))
if (newTags.length === 0) return save
return {
...save,
currentRun: {
...save.currentRun,
conceptTags: [...save.currentRun.conceptTags, ...newTags]
}
}
}
export { canEnterRoom, getUnmetPrerequisites, grantTags, hasAllPrerequisites }