131 lines
4.4 KiB
TypeScript
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);
|
|
});
|
|
});
|