import { describe, expect, it } from 'vitest'; import { distance, length } from '../../app/static/js/logic/math.js'; import { combine, encirclePoint, flankPoint, flee, keepDistance, seek, separation, steerTowards, } from '../../app/static/js/logic/steering.js'; const close = (actual, expected) => { expect(actual.x).toBeCloseTo(expected.x); expect(actual.y).toBeCloseTo(expected.y); }; describe('seek / flee', () => { it('bewegt sich mit voller Geschwindigkeit zum Ziel bzw. davon weg', () => { close(seek({ x: 0, y: 0 }, { x: 10, y: 0 }, 5), { x: 5, y: 0 }); close(flee({ x: 0, y: 0 }, { x: 0, y: 10 }, 3), { x: 0, y: -3 }); }); it('steht still, wenn es bereits am Ziel ist', () => { expect(seek({ x: 4, y: 4 }, { x: 4, y: 4 }, 100)).toEqual({ x: 0, y: 0 }); }); }); describe('separation', () => { it('stößt nahe Nachbarn ab und ignoriert entfernte', () => { const self = { x: 0, y: 0 }; const push = separation(self, [self, { x: 10, y: 0 }, { x: 500, y: 0 }], 20); expect(push.x).toBeLessThan(0); expect(push.y).toBeCloseTo(0); }); it('trennt exakt übereinanderliegende Gegner ohne NaN', () => { const push = separation({ x: 5, y: 5 }, [{ x: 5, y: 5 }], 20); expect(Number.isFinite(push.x) && Number.isFinite(push.y)).toBe(true); expect(length(push)).toBeGreaterThan(0); }); it('drückt stärker, je näher der Nachbar ist', () => { const near = separation({ x: 0, y: 0 }, [{ x: 2, y: 0 }], 20); const far = separation({ x: 0, y: 0 }, [{ x: 18, y: 0 }], 20); expect(length(near)).toBeGreaterThan(length(far)); }); }); describe('keepDistance', () => { const band = { min: 200, max: 300 }; const target = { x: 0, y: 0 }; it('nähert sich an, wenn es zu weit weg ist', () => { const v = keepDistance({ x: 500, y: 0 }, target, band, 10, 1); close(v, { x: -10, y: 0 }); }); it('weicht zurück, wenn es zu nah ist', () => { const v = keepDistance({ x: 100, y: 0 }, target, band, 10, 1); close(v, { x: 10, y: 0 }); }); it('weicht im Wohlfühlbereich seitlich aus (senkrecht zur Linie)', () => { const cw = keepDistance({ x: 250, y: 0 }, target, band, 10, 1, 0.5); const ccw = keepDistance({ x: 250, y: 0 }, target, band, 10, -1, 0.5); expect(cw.x).toBeCloseTo(0); expect(Math.abs(cw.y)).toBeCloseTo(5); expect(ccw.y).toBeCloseTo(-cw.y); }); }); describe('flankPoint / encirclePoint', () => { it('liegt im gewünschten Abstand seitlich versetzt', () => { const target = { x: 0, y: 0 }; const enemy = { x: 100, y: 0 }; const p = flankPoint(target, enemy, Math.PI / 2, 50); expect(distance(p, target)).toBeCloseTo(50); close(p, { x: 0, y: 50 }); const other = flankPoint(target, enemy, -Math.PI / 2, 50); close(other, { x: 0, y: -50 }); }); it('verteilt Umzingelungsplätze auf einem Kreis', () => { const target = { x: 10, y: 10 }; close(encirclePoint(target, 0, 100), { x: 110, y: 10 }); close(encirclePoint(target, Math.PI, 100), { x: -90, y: 10 }); }); }); describe('combine / steerTowards', () => { it('gewichtet Anteile und begrenzt auf die Höchstgeschwindigkeit', () => { const v = combine( [ { vector: { x: 10, y: 0 }, weight: 1 }, { vector: { x: 0, y: 10 }, weight: 2 }, ], 15, ); expect(length(v)).toBeCloseTo(15); expect(v.y).toBeGreaterThan(v.x); }); it('nähert sich der Wunschgeschwindigkeit schrittweise an', () => { close(steerTowards({ x: 0, y: 0 }, { x: 10, y: -10 }, 0.25), { x: 2.5, y: -2.5 }); close(steerTowards({ x: 3, y: 3 }, { x: 10, y: -10 }, 1), { x: 10, y: -10 }); }); });