import { DestructionVolume } from '../geometry/DestructionVolume'; import { validateDestructionDefinition, type DestructionDefinition } from './Destruction'; import type { Stars } from './Stars'; export interface DestructionSnapshot { elapsed:number;started:boolean;finished:boolean;complete:boolean;stars:Stars;removedPercent:number; } /** 최초 접촉 이후의 게임 시간과 남은 원본 재료 수로만 채점한다. */ export class DestructionSession { private elapsed=0; private compensation=0; private started=false; private finished=false; private complete=false; private stars:Stars=0; private remaining:number; private readonly initialCount:number; constructor(private readonly def:DestructionDefinition,initialCount?:number) { validateDestructionDefinition(def); this.initialCount=initialCount??new DestructionVolume(def).initialCount; if(!Number.isInteger(this.initialCount)||this.initialCount<=0)throw new Error('원본 재료 수가 유효하지 않음'); this.remaining=this.initialCount; } start():void {if(!this.finished)this.started=true;} advance(dt:number,remainingCount:number):void { if(!Number.isFinite(dt)||dt<0||!Number.isInteger(remainingCount)||remainingCount<0||remainingCount>this.initialCount) { throw new Error('파괴 시간 또는 남은 재료 수가 유효하지 않음'); } if(this.finished||!this.started)return; const increment=dt-this.compensation,total=this.elapsed+increment; this.compensation=(total-this.elapsed)-increment; this.elapsed=total;this.remaining=remainingCount; const [three,two,limit]=this.def.starTimes; if(remainingCount===0&&this.elapsed<=limit+1e-9) { this.complete=true;this.finished=true; this.stars=this.elapsed<=three+1e-9?3:this.elapsed<=two+1e-9?2:1; } else if(this.elapsed>=limit-1e-9){this.finished=true;this.elapsed=limit;} } snapshot():DestructionSnapshot { return {elapsed:this.elapsed,started:this.started,finished:this.finished,complete:this.complete,stars:this.stars, removedPercent:(this.initialCount-this.remaining)/this.initialCount*100}; } }