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