Files
KateLegend2_proj/tests/logic/LevelFramework.test.ts
T
2026-05-06 08:17:32 +08:00

131 lines
4.4 KiB
TypeScript

import { CameraScroller, PARALLAX_LAYERS, PARALLAX_RATIOS, cameraFromLevel } from '@logic/CameraScroller';
import { LevelMgr } from '@logic/LevelMgr';
import { EnemyType, ILevelConfig } from '@data/Interfaces';
const HORIZONTAL_LEVEL: ILevelConfig = {
id: '1-1',
chapter: 1,
displayName: '初始森林',
sceneTheme: 'forest',
scrollDirection: 'horizontal',
timeLimitSec: 75,
objective: { kind: 'kill_count', enemy: EnemyType.YaoFang, count: 3 },
levelLengthPx: 3840,
bgm: 'bgm_forest',
enemySpawns: [],
};
describe('CameraScroller — horizontal (req 8.1)', () => {
it('camera scrolls forward as player advances', () => {
const cam = cameraFromLevel(HORIZONTAL_LEVEL, 960, 540);
cam.followPlayer(480, 270);
expect(cam.offsetX).toBe(0);
cam.followPlayer(900, 270);
expect(cam.offsetX).toBe(420);
});
it('camera never rewinds on horizontal scroll (req 8.1)', () => {
const cam = cameraFromLevel(HORIZONTAL_LEVEL);
cam.followPlayer(1500, 270);
const forward = cam.offsetX;
cam.followPlayer(100, 270);
expect(cam.offsetX).toBe(forward); // did not rewind
});
it('camera stops at level end', () => {
const cam = cameraFromLevel(HORIZONTAL_LEVEL);
cam.followPlayer(10_000, 270);
expect(cam.offsetX).toBe(HORIZONTAL_LEVEL.levelLengthPx - 960);
});
});
describe('CameraScroller — parallax layers (req 8.8 — 1:2:4:4)', () => {
it('exposes 4 layers with ratios 1,2,4,4', () => {
expect(PARALLAX_LAYERS).toEqual(['far', 'mid', 'near', 'fx']);
expect([...PARALLAX_RATIOS]).toEqual([1, 2, 4, 4]);
});
it('far/mid/near produce progressively smaller offsets', () => {
const cam = cameraFromLevel(HORIZONTAL_LEVEL);
cam.followPlayer(1200, 270);
const far = cam.offsetForLayer('far');
const mid = cam.offsetForLayer('mid');
const near = cam.offsetForLayer('near');
expect(far.x).toBeGreaterThan(mid.x);
expect(mid.x).toBeGreaterThan(near.x);
});
});
describe('CameraScroller — bi-directional + vertical', () => {
it('horizontal_bi rewinds when the player walks backward', () => {
const cam = new CameraScroller({
direction: 'horizontal_bi',
lengthX: 4800,
viewportW: 960,
viewportH: 540,
});
cam.followPlayer(2000, 0);
cam.followPlayer(400, 0);
expect(cam.offsetX).toBe(0);
});
it('vertical rising clamps at top', () => {
const cam = new CameraScroller({
direction: 'vertical',
lengthX: 960,
lengthY: 3240,
viewportW: 960,
viewportH: 540,
});
cam.followPlayer(480, 6000);
expect(cam.offsetY).toBe(3240 - 540);
});
});
describe('LevelMgr — objective / timer / result', () => {
it('reports victory when kill objective is met', () => {
const lv = new LevelMgr(HORIZONTAL_LEVEL);
lv.onEnemyKilled(EnemyType.YaoFang);
lv.onEnemyKilled(EnemyType.YaoFang);
lv.onEnemyKilled(EnemyType.YaoFang);
expect(lv.tick(0.016)).toBe('victory');
});
it('reports timeout when time-limit expires', () => {
const lv = new LevelMgr(HORIZONTAL_LEVEL);
expect(lv.tick(HORIZONTAL_LEVEL.timeLimitSec + 0.1)).toBe('timeout');
});
it('player_dead is terminal and outranks victory', () => {
const lv = new LevelMgr(HORIZONTAL_LEVEL);
lv.onPlayerDied();
expect(lv.tick(0.1)).toBe('player_dead');
});
it('reach_top objective', () => {
const cfg: ILevelConfig = { ...HORIZONTAL_LEVEL, objective: { kind: 'reach_top' } };
const lv = new LevelMgr(cfg);
lv.onReachedTop();
expect(lv.tick(0.016)).toBe('victory');
});
it('defeat_boss objective', () => {
const cfg: ILevelConfig = {
...HORIZONTAL_LEVEL,
objective: { kind: 'defeat_boss', bossId: 'shuang_huan_fang' },
};
const lv = new LevelMgr(cfg);
lv.onBossKilled();
expect(lv.tick(0.016)).toBe('victory');
});
it('result() returns kills and remaining seconds', () => {
const lv = new LevelMgr(HORIZONTAL_LEVEL);
lv.onEnemyKilled(EnemyType.QingRen);
lv.tick(10);
const r = lv.result();
expect(r.kills[EnemyType.QingRen]).toBe(1);
expect(r.remainingSec).toBeCloseTo(65, 1);
});
});