import type { FileNode, FsSlice, SaveV1 } from "./types"; import { DESKTOP_ID, baseNodes, initialFsSlice } from "./state"; /** Playable levels are 0..3; 4 means finished. */ export const LAST_PLAYABLE = 3; function withDesktop(children: Omit[]): FileNode[] { const nodes = baseNodes(); for (const c of children) { nodes.push({ ...c, parentId: DESKTOP_ID }); } return nodes; } function level0Nodes(): FileNode[] { return withDesktop([ { id: "fld-docs", name: "Documents", kind: "folder" }, { id: "fld-games", name: "Games", kind: "folder" }, { id: "file-readme", name: "READ_ME.txt", kind: "file" }, ]); } function level1Nodes(): FileNode[] { return withDesktop([ { id: "fld-pics", name: "Pictures", kind: "folder" }, { id: "fld-school", name: "School", kind: "folder" }, { id: "file-cat", name: "cat.png", kind: "file" }, { id: "file-hw", name: "homework.txt", kind: "file" }, ]); } function level2Nodes(): FileNode[] { const nodes = baseNodes(); nodes.push( { id: "fld-photos", name: "Photos", parentId: DESKTOP_ID, kind: "folder" }, { id: "fld-work", name: "Work", parentId: DESKTOP_ID, kind: "folder" }, { id: "fld-installers", name: "Installers", kind: "folder", parentId: DESKTOP_ID, }, { id: "fld-downloads", name: "Downloads", kind: "folder", parentId: DESKTOP_ID, }, { id: "f-beach", name: "beach.jpg", kind: "file", parentId: "fld-downloads", }, { id: "f-budget", name: "budget.xlsx", kind: "file", parentId: "fld-downloads", }, { id: "f-game", name: "game-installer.msi", kind: "file", parentId: "fld-downloads", }, ); return nodes; } export function buildInitialFs(levelIndex: number): FsSlice { switch (levelIndex) { case 0: return initialFsSlice(level0Nodes()); case 1: return initialFsSlice(level1Nodes()); case 2: return initialFsSlice(level2Nodes()); case 3: return initialFsSlice( withDesktop([{ id: "fld-docs", name: "Documents", kind: "folder" }]), ); default: return initialFsSlice(level0Nodes()); } } export function freshSave(levelIndex: number): SaveV1 { return { version: 1, levelIndex, subIndex: 0, fs: buildInitialFs(Math.min(levelIndex, LAST_PLAYABLE)), quizChoiceId: null, }; } function parentOf(fs: FsSlice, fileId: string) { return fs.nodes.find((n) => n.id === fileId)?.parentId; } /** How many sub-challenges this main level has (quiz = 1). */ export function subCount(levelIndex: number): number { switch (levelIndex) { case 0: return 2; case 1: return 2; case 2: return 3; case 3: return 1; default: return 1; } } /** * Align subIndex with filesystem progress (fixes older saves without `subIndex`). */ export function migrateSubIndex( levelIndex: number, fs: FsSlice, _storedSub: number | undefined, ): number { const n = subCount(levelIndex); for (let i = 0; i < n; i++) { if (!isSubComplete(levelIndex, i, fs)) return i; } return Math.max(0, n - 1); } export function isSubComplete( levelIndex: number, subIndex: number, fs: FsSlice, ): boolean { switch (levelIndex) { case 0: if (subIndex === 0) return fs.visitedFolderIds.includes("fld-games"); if (subIndex === 1) return fs.visitedFolderIds.includes("fld-docs"); return false; case 1: if (subIndex === 0) return parentOf(fs, "file-cat") === "fld-pics"; if (subIndex === 1) return parentOf(fs, "file-hw") === "fld-school"; return false; case 2: if (subIndex === 0) return parentOf(fs, "f-beach") === "fld-photos"; if (subIndex === 1) return parentOf(fs, "f-budget") === "fld-work"; if (subIndex === 2) return parentOf(fs, "f-game") === "fld-installers"; return false; default: return false; } } export function isFsLevelComplete(levelIndex: number, fs: FsSlice): boolean { const n = subCount(levelIndex); for (let i = 0; i < n; i++) { if (!isSubComplete(levelIndex, i, fs)) return false; } return true; } export const QUIZ_CORRECT_ID = "opt-docs"; export function isQuizComplete(choiceId: string | null) { return choiceId === QUIZ_CORRECT_ID; } export function levelTitle(levelIndex: number): string { switch (levelIndex) { case 0: return "Find Documents"; case 1: return "Put files away"; case 2: return "Rescue Downloads"; case 3: return "Safe saving"; default: return "Complete"; } } /** Short context for the whole level. */ export function levelBlurb(levelIndex: number): string { switch (levelIndex) { case 0: return "Folders are like drawers—open them with a click. Use Back to return to the folder above."; case 1: return "Drag a file onto a folder to move it—or tap a file, then tap a folder below."; case 2: return "Drag one file at a time onto the right folder (or use tap + folder buttons)."; case 3: return "Where should you save homework so you can find it later?"; default: return ""; } } /** Current sub-challenge instruction. */ export function subBlurb(levelIndex: number, subIndex: number): string { switch (levelIndex) { case 0: if (subIndex === 0) return "First: click the Games folder to open it. Then use Back to return to the Desktop."; return "Next: open the Documents folder the same way."; case 1: if (subIndex === 0) return "Drag cat.png onto the Pictures folder (or select it, then pick Pictures)."; return "Drag homework.txt onto the School folder."; case 2: if (subIndex === 0) return "Drag beach.jpg onto Photos."; if (subIndex === 1) return "Drag budget.xlsx onto Work."; return "Drag game-installer.msi onto Installers."; case 3: return levelBlurb(3); default: return ""; } }