68 lines
3.0 KiB
TypeScript
68 lines
3.0 KiB
TypeScript
import { DropSystem } from '@logic/DropSystem';
|
|
import { EnemyType, ItemType } from '@data/Interfaces';
|
|
|
|
describe('DropSystem — crystal jade deterministic rule (req 7.1)', () => {
|
|
it('spawns a crystal jade on exactly the 12th kill', () => {
|
|
const ds = new DropSystem({ random: () => 1 });
|
|
let crystalEvents = 0;
|
|
for (let i = 1; i <= 24; i++) {
|
|
const drops = ds.onEnemyKilled(EnemyType.YaoFang, { x: 100, y: 0 });
|
|
if (drops.some((d) => d.item === ItemType.CrystalJade)) crystalEvents++;
|
|
}
|
|
expect(crystalEvents).toBe(2);
|
|
});
|
|
|
|
it('spawns the crystal above the kill point', () => {
|
|
const ds = new DropSystem({ random: () => 1 });
|
|
for (let i = 0; i < 11; i++) ds.onEnemyKilled(EnemyType.QingRen, { x: 0, y: 0 });
|
|
const drops = ds.onEnemyKilled(EnemyType.QingRen, { x: 300, y: 20 });
|
|
const crystal = drops.find((d) => d.item === ItemType.CrystalJade);
|
|
expect(crystal!.y).toBeGreaterThan(20);
|
|
});
|
|
});
|
|
|
|
describe('DropSystem — Chi Ren consecutive rule (req 7.3)', () => {
|
|
it('drops dian_wan or shu_wan on the 3rd consecutive Chi Ren kill if RNG <0.5', () => {
|
|
const ds = new DropSystem({
|
|
dianShuWanProbability: 0.5,
|
|
random: (() => {
|
|
// probability check then which-item check both pass
|
|
const vals = [0.1, 0.2];
|
|
let i = 0;
|
|
return () => vals[i++ % vals.length];
|
|
})(),
|
|
});
|
|
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
|
|
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
|
|
const drops = ds.onEnemyKilled(EnemyType.ChiRen, { x: 50, y: 10 });
|
|
expect(drops.find((d) => d.item === ItemType.DianWan)).toBeDefined();
|
|
});
|
|
|
|
it('does not drop when RNG fails probability', () => {
|
|
const ds = new DropSystem({ dianShuWanProbability: 0.5, random: () => 0.95 });
|
|
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
|
|
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
|
|
const drops = ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
|
|
expect(drops.filter((d) => d.item !== ItemType.CrystalJade).length).toBe(0);
|
|
});
|
|
|
|
it('non-Chi-Ren kill resets the consecutive counter', () => {
|
|
const ds = new DropSystem({ dianShuWanProbability: 1.0, random: () => 0 });
|
|
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
|
|
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
|
|
ds.onEnemyKilled(EnemyType.QingRen, { x: 0, y: 0 });
|
|
const drops = ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
|
|
// Only 1 Chi Ren kill since reset — below threshold.
|
|
expect(drops.filter((d) => d.item === ItemType.DianWan || d.item === ItemType.ShuWan).length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('DropSystem — reset()', () => {
|
|
it('zeroes kill counters', () => {
|
|
const ds = new DropSystem();
|
|
ds.onEnemyKilled(EnemyType.QingRen, { x: 0, y: 0 });
|
|
ds.reset();
|
|
expect(ds.kills).toBe(0);
|
|
});
|
|
});
|