refactor(api): move RandomSource to shared and add rollInclusive
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { Hunt } from './entities/hunt.entity';
|
|||||||
import { HuntEncounter } from './entities/hunt-encounter.entity';
|
import { HuntEncounter } from './entities/hunt-encounter.entity';
|
||||||
import { HuntingController } from './hunting.controller';
|
import { HuntingController } from './hunting.controller';
|
||||||
import { HuntingService } from './hunting.service';
|
import { HuntingService } from './hunting.service';
|
||||||
import { RANDOM_SOURCE, systemRandomSource } from './random-source';
|
import { RANDOM_SOURCE, systemRandomSource } from '../shared/random-source';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { HuntEncounter } from './entities/hunt-encounter.entity';
|
|||||||
import { HuntStatus } from './hunt-status.enum';
|
import { HuntStatus } from './hunt-status.enum';
|
||||||
import { HuntingDomainError } from './hunting.errors';
|
import { HuntingDomainError } from './hunting.errors';
|
||||||
import { HuntingService } from './hunting.service';
|
import { HuntingService } from './hunting.service';
|
||||||
import type { RandomSource } from './random-source';
|
import type { RandomSource } from '../shared/random-source';
|
||||||
|
|
||||||
const CHARACTER_ID = '10000000-0000-4000-8000-000000000001';
|
const CHARACTER_ID = '10000000-0000-4000-8000-000000000001';
|
||||||
const HUNTING_LOCATION_ID = '20000000-0000-4000-8000-000000000001';
|
const HUNTING_LOCATION_ID = '20000000-0000-4000-8000-000000000001';
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import {
|
|||||||
huntingNotAvailable,
|
huntingNotAvailable,
|
||||||
noHuntEncountersAvailable,
|
noHuntEncountersAvailable,
|
||||||
} from './hunting.errors';
|
} from './hunting.errors';
|
||||||
import { RANDOM_SOURCE } from './random-source';
|
import { RANDOM_SOURCE } from '../shared/random-source';
|
||||||
import type { RandomSource } from './random-source';
|
import type { RandomSource } from '../shared/random-source';
|
||||||
|
|
||||||
export interface MonsterSummary {
|
export interface MonsterSummary {
|
||||||
key: string;
|
key: string;
|
||||||
|
|||||||
28
apps/api/src/shared/roll-range.spec.ts
Normal file
28
apps/api/src/shared/roll-range.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import type { RandomSource } from './random-source';
|
||||||
|
import { rollInclusive } from './roll-range';
|
||||||
|
|
||||||
|
function fixed(...values: number[]): RandomSource {
|
||||||
|
let index = 0;
|
||||||
|
return { next: () => values[index++] };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('rollInclusive', () => {
|
||||||
|
it('maps the bottom of the random range to min and the top to max', () => {
|
||||||
|
expect(rollInclusive(fixed(0), 4, 7)).toBe(4);
|
||||||
|
expect(rollInclusive(fixed(0.999), 4, 7)).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('spreads the random range evenly across every value in between', () => {
|
||||||
|
expect(rollInclusive(fixed(0.25), 4, 7)).toBe(5);
|
||||||
|
expect(rollInclusive(fixed(0.5), 4, 7)).toBe(6);
|
||||||
|
expect(rollInclusive(fixed(0.5), 9, 15)).toBe(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('never exceeds max even if the source yields exactly 1', () => {
|
||||||
|
expect(rollInclusive(fixed(1), 9, 15)).toBe(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the single value when min equals max', () => {
|
||||||
|
expect(rollInclusive(fixed(0.7), 1, 1)).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
19
apps/api/src/shared/roll-range.ts
Normal file
19
apps/api/src/shared/roll-range.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { RandomSource } from './random-source';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rolls an inclusive integer in [min, max] from one value of `random`.
|
||||||
|
*
|
||||||
|
* `RandomSource.next()` is documented as [0, 1), but the clamp keeps a
|
||||||
|
* misbehaving or hand-stubbed source from ever exceeding `max`.
|
||||||
|
*/
|
||||||
|
export function rollInclusive(
|
||||||
|
random: RandomSource,
|
||||||
|
min: number,
|
||||||
|
max: number,
|
||||||
|
): number {
|
||||||
|
if (max <= min) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(max, min + Math.floor(random.next() * (max - min + 1)));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user