import type { JellyFillDefinition } from './JellyFill'; import { JellyFillSession } from './JellyFillSession'; import type { StageResult } from './StageSession'; import { fillContainerDefinitions } from './JellyFillContainers'; import type { Stars } from './Stars'; export interface JellyFillRunSnapshot { currentContainer: number; completedContainers: number; totalContainers: number } /** 모든 통의 성공 수를 집계한다. 실패해도 통의 출입 연출은 끝까지 이어진다. */ export class JellyFillRun { private current: JellyFillSession; private index = 1; private completed = 0; private sealed = false; private stars: Stars = 3; private readonly total: number; private readonly sessions: readonly JellyFillSession[]; readonly definitions: readonly Readonly[]; constructor(definition: JellyFillDefinition) { this.total = definition.containers ?? 1; if (!Number.isInteger(this.total) || this.total < 1 || this.total > 12) throw new Error('담을 통 수가 유효하지 않음'); this.sessions=fillContainerDefinitions(definition).map(d=>new JellyFillSession(d)); this.definitions=this.sessions.map(s=>s.definition); this.current = this.sessions[0]; } get session(): JellyFillSession { return this.current; } get nextDefinition(): Readonly | null { return this.sessions[this.index]?.definition ?? null; } finishCurrent(): void { const outcome = this.current.snapshot().outcome; if (!outcome || this.sealed) return; this.sealed = true; if (outcome === 'clear') this.completed++; const stars = this.current.getResult().stars; if (stars < this.stars) this.stars = stars; } get canAdvance(): boolean { return this.sealed && this.index < this.total; } advanceNext(): boolean { if (!this.canAdvance) return false; this.current = this.sessions[this.index]; this.index++; this.sealed = false; return true; } snapshot(): JellyFillRunSnapshot { return { currentContainer: this.index, completedContainers: this.completed, totalContainers: this.total }; } getResult(): StageResult { const clear = this.sealed && this.completed === this.total, accuracy = this.completed / this.total * 100; return { accuracy, displayAccuracy: `${Math.floor(accuracy)}`, stars: clear ? this.stars : 0, isClear: clear, pieceCount: this.completed, targetPieceCount: this.total, guideScores: {} }; } }