Полный рефакторинг проекта в стабильную расширяемую базу рогалика. Почему: после разнесения single-file на модули потерялся вызов генерации карты (RoomMap не генерировал комнаты) → игра падала на старте; баг скрывался тем, что Bun-бандлер не проверяет типы. Архитектура: - Логика игры (src/core/) полностью отделена от рендера: без three.js и DOM, тестируется без браузера. - Рендер мира на three.js с ортокамерой (2D-вид); HUD/миникарта — 2D-канвас поверх. - Фиксированный игровой цикл 60 Гц + интерполяция (раньше скорость зависела от частоты кадров). - Seeded-RNG, ввод через абстрактные «намерения» (InputState). Возможности: - Стартовое меню с выбором уровня (Esc → меню). - Конфигуратор уровней: LevelRules + 5 пресетов (размер карты, плотность/сила врагов, HP, фиксированный seed). - Тема внешнего вида (render/theme.ts) — задел под кастомные ассеты. Качество: - 27 юнит-тестов ядра (генерация, симметрия дверей, коллизии, спавн, правила). - Два круга adversarial-ревью; исправлено 6 реальных багов (кнокбэк сквозь стены → софт-лок; незакрываемая сокровищница; фикс-сид после рестарта; перенос ввода между забегами; нет source maps; неточности в доках). - Документация: README, docs/ARCHITECTURE.md, CLAUDE.md, docs/HOWTO.md. - dist/ исключён из гита; bun.lock зафиксирован. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
import { describe, it, expect } from 'bun:test';
|
|
import { Rng } from '../src/core/rng';
|
|
|
|
describe('Rng', () => {
|
|
it('детерминирован: одинаковый seed → одинаковая последовательность', () => {
|
|
const a = new Rng(12345);
|
|
const b = new Rng(12345);
|
|
const seqA = Array.from({ length: 10 }, () => a.next());
|
|
const seqB = Array.from({ length: 10 }, () => b.next());
|
|
expect(seqA).toEqual(seqB);
|
|
});
|
|
|
|
it('разные seed дают разные последовательности', () => {
|
|
const a = new Rng(1);
|
|
const b = new Rng(2);
|
|
expect(a.next()).not.toBe(b.next());
|
|
});
|
|
|
|
it('next() всегда в [0, 1)', () => {
|
|
const r = new Rng(7);
|
|
for (let i = 0; i < 1000; i++) {
|
|
const v = r.next();
|
|
expect(v).toBeGreaterThanOrEqual(0);
|
|
expect(v).toBeLessThan(1);
|
|
}
|
|
});
|
|
|
|
it('int(a, b) держится в границах включительно', () => {
|
|
const r = new Rng(99);
|
|
for (let i = 0; i < 1000; i++) {
|
|
const v = r.int(3, 7);
|
|
expect(v).toBeGreaterThanOrEqual(3);
|
|
expect(v).toBeLessThanOrEqual(7);
|
|
expect(Number.isInteger(v)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('shuffle сохраняет все элементы', () => {
|
|
const r = new Rng(42);
|
|
const arr = [1, 2, 3, 4, 5];
|
|
const shuffled = r.shuffle([...arr]);
|
|
expect([...shuffled].sort()).toEqual(arr);
|
|
});
|
|
});
|