boardgame-core/tests/utils/rng.test.ts

137 lines
4.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { createRNG, Mulberry32RNG } from '@/utils/rng';
describe('createRNG', () => {
it('should create RNG with default seed', () => {
const rng = createRNG();
expect(rng.getSeed()).toBe(1);
});
it('should create RNG with custom seed', () => {
const rng = createRNG(12345);
expect(rng.getSeed()).toBe(12345);
});
it('should generate numbers in range [0, 1)', () => {
const rng = createRNG(42);
for (let i = 0; i < 100; i++) {
const num = rng.next();
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
}
});
it('should generate numbers with max parameter', () => {
const rng = createRNG(42);
for (let i = 0; i < 100; i++) {
const num = rng.next(100);
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(100);
}
});
it('should generate integers in range [0, max)', () => {
const rng = createRNG(42);
for (let i = 0; i < 100; i++) {
const num = rng.nextInt(10);
expect(Number.isInteger(num)).toBe(true);
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(10);
}
});
it('should be deterministic with same seed', () => {
const rng1 = createRNG(12345);
const rng2 = createRNG(12345);
const sequence1 = Array.from({ length: 10 }, () => rng1.next());
const sequence2 = Array.from({ length: 10 }, () => rng2.next());
expect(sequence1).toEqual(sequence2);
});
it('should produce different sequences with different seeds', () => {
const rng1 = createRNG(12345);
const rng2 = createRNG(54321);
const sequence1 = Array.from({ length: 10 }, () => rng1.next());
const sequence2 = Array.from({ length: 10 }, () => rng2.next());
expect(sequence1).not.toEqual(sequence2);
});
it('should reset seed with setSeed', () => {
const rng = createRNG(42);
const firstSequence = Array.from({ length: 5 }, () => rng.next());
rng.setSeed(42);
const secondSequence = Array.from({ length: 5 }, () => rng.next());
expect(firstSequence).toEqual(secondSequence);
});
it('should generate uniformly distributed integers', () => {
const rng = createRNG(42);
const buckets = new Array(10).fill(0);
const iterations = 10000;
for (let i = 0; i < iterations; i++) {
const num = rng.nextInt(10);
buckets[num]++;
}
// 每个桶应该大约有 10% 的值
const expected = iterations / 10;
const tolerance = expected * 0.3; // 30% 容差
buckets.forEach((count, index) => {
expect(count).toBeGreaterThan(expected - tolerance);
expect(count).toBeLessThan(expected + tolerance);
});
});
});
describe('Mulberry32RNG', () => {
it('should instantiate with default seed', () => {
const rng = new Mulberry32RNG();
expect(rng.getSeed()).toBe(1);
});
it('should instantiate with custom seed', () => {
const rng = new Mulberry32RNG(99999);
expect(rng.getSeed()).toBe(99999);
});
it('should implement RNG interface', () => {
const rng = new Mulberry32RNG(42);
// Should have all RNG methods
expect(typeof rng.next).toBe('function');
expect(typeof rng.nextInt).toBe('function');
expect(typeof rng.setSeed).toBe('function');
expect(typeof rng.getSeed).toBe('function');
});
it('should produce same results as createRNG with same seed', () => {
const factoryRng = createRNG(12345);
const directRng = new Mulberry32RNG(12345);
for (let i = 0; i < 10; i++) {
expect(factoryRng.next()).toBe(directRng.next());
expect(factoryRng.nextInt(100)).toBe(directRng.nextInt(100));
}
});
it('should allow seed changes after instantiation', () => {
const rng = new Mulberry32RNG(100);
expect(rng.getSeed()).toBe(100);
rng.setSeed(200);
expect(rng.getSeed()).toBe(200);
const value = rng.next();
expect(value).toBeGreaterThanOrEqual(0);
expect(value).toBeLessThan(1);
});
});