first commmit
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { ChiRenAI, EnemyManager, HeiRenAI, QingRenAI, YaoFangAI } from '@logic/EnemyAI';
|
||||
import { EnemyType, IEnemyConfig } from '@data/Interfaces';
|
||||
|
||||
function cfg(id: EnemyType, intervalSec: number, speed = 0): IEnemyConfig {
|
||||
return {
|
||||
id,
|
||||
displayName: id,
|
||||
size: { w: 16, h: 16 },
|
||||
moveSpeed: speed,
|
||||
attackIntervalSec: intervalSec,
|
||||
attacks: ['shuriken'],
|
||||
hp: 1,
|
||||
};
|
||||
}
|
||||
|
||||
const idlePlayer = { x: 300, y: 16, isGrounded: true };
|
||||
|
||||
describe('QingRenAI (req 6.1)', () => {
|
||||
it('throws a shuriken at the player when out of melee range', () => {
|
||||
const ai = new QingRenAI(cfg(EnemyType.QingRen, 2.0), 0, 16);
|
||||
const actions = ai.update({ dtSec: 2.0, nowMs: 0, player: idlePlayer });
|
||||
expect(actions[0].kind).toBe('fire_bullet');
|
||||
expect(actions[0].attackType).toBe('shuriken');
|
||||
expect(actions[0].velX).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('melee swings when player is close', () => {
|
||||
const ai = new QingRenAI(cfg(EnemyType.QingRen, 2.0), 310, 16);
|
||||
const actions = ai.update({ dtSec: 2.0, nowMs: 0, player: idlePlayer });
|
||||
expect(actions[0].kind).toBe('melee_swing');
|
||||
});
|
||||
|
||||
it('respects attack interval — no burst within one tick', () => {
|
||||
const ai = new QingRenAI(cfg(EnemyType.QingRen, 2.0), 0, 16);
|
||||
ai.update({ dtSec: 2.0, nowMs: 0, player: idlePlayer });
|
||||
const actions = ai.update({ dtSec: 0.1, nowMs: 0, player: idlePlayer });
|
||||
expect(actions.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChiRenAI (req 6.2-6.3)', () => {
|
||||
it('moves horizontally toward the player at 120 px/s', () => {
|
||||
const ai = new ChiRenAI(cfg(EnemyType.ChiRen, 1.5, 120), 0, 16);
|
||||
ai.update({ dtSec: 1, nowMs: 0, player: idlePlayer });
|
||||
expect(ai.pos.x).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('throws smoke bombs at the configured cadence', () => {
|
||||
const ai = new ChiRenAI(cfg(EnemyType.ChiRen, 1.5, 120), 0, 16);
|
||||
const actions = ai.update({ dtSec: 1.5, nowMs: 0, player: idlePlayer });
|
||||
expect(actions.some((a) => a.attackType === 'smoke_bomb')).toBe(true);
|
||||
});
|
||||
|
||||
it('bumps upward when the player is idle within intercept range', () => {
|
||||
const ai = new ChiRenAI(cfg(EnemyType.ChiRen, 1.5, 120), 250, 16);
|
||||
const before = ai.pos.y;
|
||||
ai.update({ dtSec: 0.016, nowMs: 0, player: idlePlayer });
|
||||
expect(ai.pos.y).toBeGreaterThan(before);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HeiRenAI (req 6.5)', () => {
|
||||
it('drops exactly one magic flute on kill', () => {
|
||||
const ai = new HeiRenAI(cfg(EnemyType.HeiRen, 2.5), 100, 16);
|
||||
const firstDrop = ai.onKilled();
|
||||
const secondDrop = ai.onKilled();
|
||||
expect(firstDrop[0].itemId).toBe('mo_di');
|
||||
expect(secondDrop.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('YaoFangAI (req 6.6)', () => {
|
||||
it('launches straight-line fireballs at 3s cadence', () => {
|
||||
const ai = new YaoFangAI(cfg(EnemyType.YaoFang, 3.0), 0, 16);
|
||||
const a = ai.update({ dtSec: 3.0, nowMs: 0, player: idlePlayer });
|
||||
expect(a[0].attackType).toBe('fireball');
|
||||
});
|
||||
});
|
||||
|
||||
describe('EnemyManager culling (req 6.7)', () => {
|
||||
it('skips update for enemies outside the camera cull rect', () => {
|
||||
const mgr = new EnemyManager();
|
||||
const far = new YaoFangAI(cfg(EnemyType.YaoFang, 3.0), 5000, 16);
|
||||
mgr.spawn(far);
|
||||
const actions = mgr.update(3.0, 0, idlePlayer, {
|
||||
leftX: 0,
|
||||
rightX: 960,
|
||||
topY: 540,
|
||||
bottomY: 0,
|
||||
});
|
||||
expect(actions.length).toBe(0);
|
||||
});
|
||||
|
||||
it('updates enemies inside the cull rect', () => {
|
||||
const mgr = new EnemyManager();
|
||||
const near = new YaoFangAI(cfg(EnemyType.YaoFang, 3.0), 200, 16);
|
||||
mgr.spawn(near);
|
||||
const actions = mgr.update(3.0, 0, idlePlayer, {
|
||||
leftX: 0,
|
||||
rightX: 960,
|
||||
topY: 540,
|
||||
bottomY: 0,
|
||||
});
|
||||
expect(actions.length).toBe(1);
|
||||
expect(actions[0].kind).toBe('fire_bullet');
|
||||
});
|
||||
|
||||
it('kill() converts Hei Ren to a magic-flute drop', () => {
|
||||
const mgr = new EnemyManager();
|
||||
const hei = new HeiRenAI(cfg(EnemyType.HeiRen, 2.5), 50, 16);
|
||||
mgr.spawn(hei);
|
||||
const drops = mgr.kill(hei);
|
||||
expect(drops[0].itemId).toBe('mo_di');
|
||||
expect(hei.alive).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user