first commmit

This commit is contained in:
jakciehan
2026-05-06 08:17:32 +08:00
commit 427a33c55b
269 changed files with 20906 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
import { TutorialMgr } from '@logic/TutorialMgr';
import { ScoreSystem, BASE_ENEMY_SCORE, COMBO_BONUS } from '@logic/ScoreSystem';
import { WeaponType } from '@data/Interfaces';
import { StorageMgr } from '@common/StorageMgr';
function mem() {
const m = new Map<string, string>();
return {
getItem: (k: string) => (m.has(k) ? (m.get(k) as string) : null),
setItem: (k: string, v: string) => {
m.set(k, v);
},
removeItem: (k: string) => {
m.delete(k);
},
};
}
describe('TutorialMgr — built-in sequences for 1-1..1-3 (req 11.1-11.3)', () => {
it('maybeStart returns the first step of 1-1', () => {
const t = new TutorialMgr(new StorageMgr(mem()));
const step = t.maybeStart('1-1');
expect(step).toBeDefined();
expect(step!.id).toBe('attack');
});
it('reportAction advances through the sequence', () => {
const t = new TutorialMgr(new StorageMgr(mem()));
t.maybeStart('1-1');
expect(t.reportAction('fire_shuriken')).toMatchObject({ id: 'joystick' });
expect(t.reportAction('move')).toMatchObject({ id: 'jump' });
expect(t.reportAction('jump')).toBe('finished');
});
it('completed tutorials are persisted and skipped on replay (req 11.4)', () => {
const storage = new StorageMgr(mem());
const t = new TutorialMgr(storage);
t.maybeStart('1-1');
t.reportAction('fire_shuriken');
t.reportAction('move');
t.reportAction('jump');
expect(t.isCompleted('1-1')).toBe(true);
expect(t.maybeStart('1-1')).toBeNull();
});
it('resetAll clears the completion set (req 11.5)', () => {
const t = new TutorialMgr(new StorageMgr(mem()));
t.maybeStart('1-2');
t.reportAction('parabolic_jump');
t.reportAction('attack_switch');
t.reportAction('parry');
t.reportAction('jump_attack');
t.reportAction('pickup_crystal');
t.resetAll();
expect(t.maybeStart('1-2')).toBeDefined();
});
it('reportAction with wrong action is a no-op', () => {
const t = new TutorialMgr(new StorageMgr(mem()));
t.maybeStart('1-1');
expect(t.reportAction('wrong')).toBe('no_op');
expect(t.currentStep()!.id).toBe('attack');
});
});
describe('ScoreSystem — scoring table (req 12.1-12.6)', () => {
it('sword kill is ×2, shuriken kill is ×1', () => {
const s = new ScoreSystem();
s.recordEnemyKill(WeaponType.NinjaSword);
s.recordEnemyKill(WeaponType.Shuriken);
expect(s.snapshot().baseScore).toBe(BASE_ENEMY_SCORE * 3);
});
it('parry kill is ×3 base', () => {
const s = new ScoreSystem();
s.recordParryKill();
expect(s.snapshot().baseScore).toBe(BASE_ENEMY_SCORE * 3);
});
it('5 blade contacts award a +1500 combo bonus (req 12.4)', () => {
const s = new ScoreSystem();
for (let i = 0; i < 5; i++) s.recordBladeContact();
const snap = s.snapshot();
expect(snap.comboBonus).toBe(COMBO_BONUS);
expect(snap.comboCount).toBe(1);
expect(snap.consecutiveBladeHits).toBe(0);
});
it('breakBladeChain resets the streak', () => {
const s = new ScoreSystem();
s.recordBladeContact();
s.recordBladeContact();
s.breakBladeChain();
s.recordBladeContact();
expect(s.snapshot().consecutiveBladeHits).toBe(1);
});
it('flawless run triples total score; taking damage removes the bonus (req 12.5)', () => {
const s = new ScoreSystem();
s.recordEnemyKill(WeaponType.Shuriken);
const flawlessSnap = s.snapshot();
expect(flawlessSnap.flawlessMultiplier).toBe(3);
s.markTaken();
const damagedSnap = s.snapshot();
expect(damagedSnap.flawlessMultiplier).toBe(1);
});
it('remaining time bonus adds 10 pts / sec (req 12.6)', () => {
const s = new ScoreSystem();
s.recordEnemyKill(WeaponType.Shuriken);
s.setRemainingTimeBonus(30);
expect(s.snapshot().timeBonus).toBe(300);
});
});