refactor: rename RNG method from nextFloat to nextFloat1 for consistency across the codebase
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
resolveGeneralAction,
|
||||
} from '@sammo-ts/logic';
|
||||
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic';
|
||||
import { asRecord, LiteHashDRBG } from '@sammo-ts/common';
|
||||
import { asRecord, LiteHashDRBG, RandUtil } from '@sammo-ts/common';
|
||||
|
||||
import type { ConstraintContext, StateView } from '@sammo-ts/logic';
|
||||
|
||||
@@ -76,31 +76,6 @@ const serializeSeed = (...values: Array<string | number>): string =>
|
||||
.map((value) => (typeof value === 'string' ? `str(${value.length},${value})` : `int(${Math.floor(value)})`))
|
||||
.join('|');
|
||||
|
||||
class DeterministicRandom {
|
||||
constructor(private readonly rng: LiteHashDRBG) {}
|
||||
|
||||
nextFloat(): number {
|
||||
return this.rng.nextFloat1();
|
||||
}
|
||||
|
||||
nextBool(probability: number): boolean {
|
||||
if (probability >= 1) {
|
||||
return true;
|
||||
}
|
||||
if (probability <= 0) {
|
||||
return false;
|
||||
}
|
||||
return this.nextFloat() < probability;
|
||||
}
|
||||
|
||||
nextInt(minInclusive: number, maxExclusive: number): number {
|
||||
const span = maxExclusive - minInclusive;
|
||||
if (span <= 1) {
|
||||
return minInclusive;
|
||||
}
|
||||
return minInclusive + this.rng.nextInt(span - 1);
|
||||
}
|
||||
}
|
||||
|
||||
type WorldView = {
|
||||
getGeneralById(id: number): TurnGeneral | null;
|
||||
@@ -539,7 +514,7 @@ export const createReservedTurnHandler = async (options: {
|
||||
context.world.currentMonth,
|
||||
currentGeneral.id
|
||||
);
|
||||
return new DeterministicRandom(new LiteHashDRBG(rngSeed));
|
||||
return new RandUtil(new LiteHashDRBG(rngSeed));
|
||||
};
|
||||
|
||||
const actionArgsRecord = extractArgsRecord(actionArgs);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface RandomGenerator {
|
||||
nextFloat(): number;
|
||||
nextFloat1(): number;
|
||||
nextBool(probability: number): boolean;
|
||||
nextInt(minInclusive: number, maxExclusive: number): number;
|
||||
}
|
||||
|
||||
@@ -13,13 +13,17 @@ export class RandUtil {
|
||||
return this.nextFloat1() * range + min;
|
||||
}
|
||||
|
||||
public nextRangeInt(min: number, max: number): number {
|
||||
const range = max - min;
|
||||
return this.rng.nextInt(range) + min;
|
||||
public nextRangeInt(minInclusive: number, maxInclusive: number): number {
|
||||
const range = maxInclusive - minInclusive;
|
||||
return this.rng.nextInt(range) + minInclusive;
|
||||
}
|
||||
|
||||
public nextInt(max?: number): number {
|
||||
return this.rng.nextInt(max);
|
||||
public nextInt(minInclusive: number, maxExclusive: number): number {
|
||||
const span = maxExclusive - minInclusive;
|
||||
if (span <= 1) {
|
||||
return minInclusive;
|
||||
}
|
||||
return minInclusive + this.rng.nextInt(span - 1);
|
||||
}
|
||||
|
||||
public nextBit(): boolean {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ScenarioMeta } from '@sammo-ts/logic/world/types.js';
|
||||
import type { MapDefinition, UnitSetDefinition } from '@sammo-ts/logic/world/types.js';
|
||||
|
||||
export interface ActionRandomSource {
|
||||
nextFloat(): number;
|
||||
nextFloat1(): number;
|
||||
nextBool(probability: number): boolean;
|
||||
nextInt(minInclusive: number, maxExclusive: number): number;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ const pickByWeight = (rng: GeneralActionResolveContext['rng']): { flags: number;
|
||||
const text = base.texts[0] ?? '';
|
||||
return { flags: base.flags, text };
|
||||
}
|
||||
let cursor = rng.nextFloat() * total;
|
||||
let cursor = rng.nextFloat1() * total;
|
||||
for (const entry of SIGHTSEEING_MESSAGES) {
|
||||
const weight = Math.max(entry.weight, 0);
|
||||
cursor -= weight;
|
||||
|
||||
@@ -47,7 +47,7 @@ const pickByWeight = <T extends string>(rng: DrillContext['rng'], weights: Recor
|
||||
if (total <= 0) {
|
||||
return first[0];
|
||||
}
|
||||
let cursor = rng.nextFloat() * total;
|
||||
let cursor = rng.nextFloat1() * total;
|
||||
for (const [key, weight] of entries) {
|
||||
if (weight <= 0) {
|
||||
continue;
|
||||
|
||||
@@ -91,7 +91,7 @@ const pickUsingWeightPair = <T>(rng: RandomGenerator, items: Array<[T, number]>)
|
||||
if (total <= 0) {
|
||||
return items[0]?.[0] ?? null;
|
||||
}
|
||||
let cursor = rng.nextFloat() * total;
|
||||
let cursor = rng.nextFloat1() * total;
|
||||
for (const [item, weight] of items) {
|
||||
if (weight <= 0) {
|
||||
continue;
|
||||
|
||||
@@ -53,7 +53,7 @@ export class ActionResolver<
|
||||
// For now, assume simplified or no bonus unless strictly required to port helper.
|
||||
// Assuming 1.0 for now if helper not available, or implement simple bonus.
|
||||
// Legacy: $score *= $rng->nextRange(0.8, 1.2);
|
||||
score *= context.rng.nextFloat() * 0.4 + 0.8;
|
||||
score *= context.rng.nextFloat1() * 0.4 + 0.8;
|
||||
|
||||
// 3. Success/Fail Ratio
|
||||
let successRatio = 0.1;
|
||||
@@ -63,7 +63,7 @@ export class ActionResolver<
|
||||
failRatio = this.pipeline.onCalcDomestic(context, '조달', 'fail', failRatio);
|
||||
|
||||
// 4. Determine Outcome
|
||||
const roll = context.rng.nextFloat();
|
||||
const roll = context.rng.nextFloat1();
|
||||
let outcome: 'fail' | 'success' | 'normal' = 'normal';
|
||||
|
||||
if (roll < failRatio) {
|
||||
@@ -95,7 +95,7 @@ export class ActionResolver<
|
||||
// Stat Exp
|
||||
// Legacy: choose weighted among L/S/I
|
||||
const statChoice =
|
||||
context.rng.nextFloat() * (general.stats.leadership + general.stats.strength + general.stats.intelligence);
|
||||
context.rng.nextFloat1() * (general.stats.leadership + general.stats.strength + general.stats.intelligence);
|
||||
let statKey: 'leadership_exp' | 'strength_exp' | 'intel_exp' = 'leadership_exp';
|
||||
|
||||
if (statChoice < general.stats.leadership) {
|
||||
|
||||
@@ -68,14 +68,14 @@ const getMetaNumber = (meta: Record<string, unknown>, key: string): number | nul
|
||||
return typeof raw === 'number' ? raw : null;
|
||||
};
|
||||
|
||||
const randomRange = (rng: RandomGenerator, min: number, max: number): number => min + (max - min) * rng.nextFloat();
|
||||
const randomRange = (rng: RandomGenerator, min: number, max: number): number => min + (max - min) * rng.nextFloat1();
|
||||
|
||||
const pickByWeight = (rng: RandomGenerator, weights: Record<DomesticCriticalPick, number>): DomesticCriticalPick => {
|
||||
const total = weights.fail + weights.normal + weights.success;
|
||||
if (total <= 0) {
|
||||
return 'normal';
|
||||
}
|
||||
let cursor = rng.nextFloat() * total;
|
||||
let cursor = rng.nextFloat1() * total;
|
||||
for (const key of ['fail', 'normal', 'success'] as const) {
|
||||
cursor -= weights[key];
|
||||
if (cursor <= 0) {
|
||||
|
||||
@@ -114,7 +114,7 @@ const pickByWeight = <T extends string>(rng: RandomGenerator, weights: Record<T,
|
||||
if (total <= 0) {
|
||||
return first[0];
|
||||
}
|
||||
let cursor = rng.nextFloat() * total;
|
||||
let cursor = rng.nextFloat1() * total;
|
||||
for (const [key, weight] of entries) {
|
||||
if (weight <= 0) {
|
||||
continue;
|
||||
|
||||
@@ -47,7 +47,7 @@ const addMetaNumber = (meta: Record<string, unknown>, key: string, delta: number
|
||||
return { ...meta, [key]: current + delta };
|
||||
};
|
||||
|
||||
const randomRange = (rng: RandomGenerator, min: number, max: number): number => min + (max - min) * rng.nextFloat();
|
||||
const randomRange = (rng: RandomGenerator, min: number, max: number): number => min + (max - min) * rng.nextFloat1();
|
||||
|
||||
const remainCityTrust = (): Constraint => ({
|
||||
name: 'remainCityTrust',
|
||||
|
||||
@@ -68,16 +68,16 @@ export const resolveTournamentBattle = (input: TournamentBattleInput): Tournamen
|
||||
let selected = 2;
|
||||
|
||||
for (let phase = 1; phase <= maxTurns; phase += 1) {
|
||||
const baseDamageAttacker = round(defenderStat * (rng.nextInt(21) + 90) / 130);
|
||||
const baseDamageDefender = round(attackerStat * (rng.nextInt(21) + 90) / 130);
|
||||
const baseDamageAttacker = round(defenderStat * (rng.nextInt(0, 22) + 90) / 130);
|
||||
const baseDamageDefender = round(attackerStat * (rng.nextInt(0, 22) + 90) / 130);
|
||||
let damageAttacker = baseDamageAttacker;
|
||||
let damageDefender = baseDamageDefender;
|
||||
|
||||
if (attackerStat >= rng.nextInt(100)) {
|
||||
damageDefender += round(attackerStat * (rng.nextInt(41) + 10) / 130);
|
||||
if (attackerStat >= rng.nextInt(0, 101)) {
|
||||
damageDefender += round(attackerStat * (rng.nextInt(0, 42) + 10) / 130);
|
||||
}
|
||||
if (defenderStat >= rng.nextInt(100)) {
|
||||
damageAttacker += round(defenderStat * (rng.nextInt(41) + 10) / 130);
|
||||
if (defenderStat >= rng.nextInt(0, 101)) {
|
||||
damageAttacker += round(defenderStat * (rng.nextInt(0, 42) + 10) / 130);
|
||||
}
|
||||
|
||||
let criticalAttacker = false;
|
||||
@@ -85,13 +85,13 @@ export const resolveTournamentBattle = (input: TournamentBattleInput): Tournamen
|
||||
let factorAttacker = 1;
|
||||
let factorDefender = 1;
|
||||
|
||||
if (energyBaseAttacker / 5 > energyAttacker && damageAttacker > damageDefender && attackerStat >= rng.nextInt(300)) {
|
||||
factorDefender = round((rng.nextInt(301) + 200) / 100);
|
||||
if (energyBaseAttacker / 5 > energyAttacker && damageAttacker > damageDefender && attackerStat >= rng.nextInt(0, 301)) {
|
||||
factorDefender = round((rng.nextInt(0, 302) + 200) / 100);
|
||||
criticalAttacker = true;
|
||||
log.push(`<S>●</> <Y>${attacker.name}</>의 분노의 일격!`);
|
||||
}
|
||||
if (energyBaseDefender / 5 > energyDefender && damageDefender > damageAttacker && defenderStat >= rng.nextInt(300)) {
|
||||
factorAttacker = round((rng.nextInt(301) + 200) / 100);
|
||||
if (energyBaseDefender / 5 > energyDefender && damageDefender > damageAttacker && defenderStat >= rng.nextInt(0, 301)) {
|
||||
factorAttacker = round((rng.nextInt(0, 302) + 200) / 100);
|
||||
criticalDefender = true;
|
||||
log.push(`<S>●</> <Y>${defender.name}</>의 분노의 일격!`);
|
||||
}
|
||||
@@ -100,18 +100,18 @@ export const resolveTournamentBattle = (input: TournamentBattleInput): Tournamen
|
||||
damageDefender = round(damageDefender * factorDefender);
|
||||
|
||||
if (phase === 1) {
|
||||
if (attackerStat * 0.9 > defenderStat && attackerStat >= rng.nextInt(400)) {
|
||||
damageDefender += round(attackerStat * (rng.nextInt(31) + 70) / 100);
|
||||
if (attackerStat * 0.9 > defenderStat && attackerStat >= rng.nextInt(0, 401)) {
|
||||
damageDefender += round(attackerStat * (rng.nextInt(0, 32) + 70) / 100);
|
||||
}
|
||||
if (defenderStat * 0.9 > attackerStat && defenderStat >= rng.nextInt(400)) {
|
||||
damageAttacker += round(defenderStat * (rng.nextInt(31) + 70) / 100);
|
||||
if (defenderStat * 0.9 > attackerStat && defenderStat >= rng.nextInt(0, 401)) {
|
||||
damageAttacker += round(defenderStat * (rng.nextInt(0, 32) + 70) / 100);
|
||||
}
|
||||
} else {
|
||||
if (!criticalAttacker && attackerStat >= rng.nextInt(1000)) {
|
||||
damageDefender += round(attackerStat * (rng.nextInt(31) + 20) / 100);
|
||||
if (!criticalAttacker && attackerStat >= rng.nextInt(0, 1001)) {
|
||||
damageDefender += round(attackerStat * (rng.nextInt(0, 32) + 20) / 100);
|
||||
}
|
||||
if (!criticalDefender && defenderStat >= rng.nextInt(1000)) {
|
||||
damageAttacker += round(defenderStat * (rng.nextInt(31) + 20) / 100);
|
||||
if (!criticalDefender && defenderStat >= rng.nextInt(0, 1001)) {
|
||||
damageAttacker += round(defenderStat * (rng.nextInt(0, 32) + 20) / 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ const aftermathConfig: WarAftermathConfig = {
|
||||
};
|
||||
|
||||
const rng = {
|
||||
nextFloat: () => 0.1,
|
||||
nextFloat1: () => 0.1,
|
||||
nextBool: (probability: number) => probability >= 0.1,
|
||||
nextInt: (minInclusive: number, _maxExclusive: number) => minInclusive,
|
||||
};
|
||||
|
||||
@@ -175,7 +175,7 @@ describe('General Commands New Scenario', () => {
|
||||
nextBool: () => true,
|
||||
nextRange: (_min: number, max: number) => max,
|
||||
nextRangeInt: (_min: number, max: number) => max,
|
||||
nextFloat: () => 0.9, // Ensure success
|
||||
nextFloat1: () => 0.9, // Ensure success
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -121,7 +121,7 @@ class ReservedTurnRunner extends TestGameRunner {
|
||||
|
||||
// Simple RNG mock
|
||||
const rng: RandomGenerator = {
|
||||
nextFloat: () => 0.5,
|
||||
nextFloat1: () => 0.5,
|
||||
nextBool: () => true,
|
||||
nextInt: (min: number, _max: number) => min,
|
||||
};
|
||||
|
||||
@@ -229,7 +229,7 @@ describe('trait modules', () => {
|
||||
listGenerals: () => [general, patient],
|
||||
};
|
||||
const rng: RandomGenerator = {
|
||||
nextFloat: () => 0,
|
||||
nextFloat1: () => 0,
|
||||
nextBool: () => true,
|
||||
nextInt: (minInclusive: number, _maxExclusive: number) => minInclusive,
|
||||
};
|
||||
|
||||
@@ -211,7 +211,7 @@ export class TestGameRunner {
|
||||
nextBool: () => Math.random() < 0.5,
|
||||
nextRange: (min: number, max: number) => Math.random() * (max - min) + min,
|
||||
nextRangeInt: (min: number, max: number) => Math.floor(Math.random() * (max - min)) + min,
|
||||
nextFloat: () => Math.random(),
|
||||
nextFloat1: () => Math.random(),
|
||||
} as any,
|
||||
|
||||
year: this.currentDate.getFullYear(),
|
||||
|
||||
Reference in New Issue
Block a user