import { Generator, Arbitrary } from '../Generator' import { WeightedValue, normalizeWeightedValues } from '../combinator/elementof' import { Random } from '../Random' import { Action, ActionGen, SimpleAction, SimpleActionGen } from './statefulbase' /** * Factory returning a generator for SimpleActions for a given object. * @template ObjectType The type of the object. */ export type SimpleActionGenFactory = (obj: ObjectType) => Generator> /** * Factory returning a generator for Actions for a given object and model. * @template ObjectType The type of the object. * @template ModelType The type of the model. */ export type ActionGenFactory = ( obj: ObjectType, model: ModelType ) => Generator> /** Union of SimpleActionGenFactory and SimpleActionGen. */ export type SimpleActionGenOrFactory = SimpleActionGenFactory | SimpleActionGen /** Union of ActionGenFactory and ActionGen. */ export type ActionGenOrFactory = | ActionGenFactory | ActionGen // Type guard for ActionGen (checks for generate method). function isActionGen( element: ActionGenOrFactory ): element is ActionGen { return (element as ActionGen).generate !== undefined } // Type guard for SimpleActionGen (checks for generate method). function isSimpleActionGen( element: SimpleActionGenOrFactory ): element is SimpleActionGen { return (element as SimpleActionGen).generate !== undefined } /** * Creates a SimpleActionGenFactory combining multiple weighted SimpleActionGen or SimpleActionGenFactory instances. * * @template ObjectType The type of the object. * @param simpleActionGenFactories Array of SimpleActionGen, SimpleActionGenFactory, or weighted versions. * @returns A SimpleActionGenFactory selecting based on weights. * * @example * ```ts * const choose = Gen.simpleActionGenOf( * Gen.just(new SimpleAction((xs: number[]) => xs.push(1), 'push')), * Gen.weightedValue((xs: number[]) => Gen.just(new SimpleAction(() => xs.pop(), 'pop')), 0.3) * ) * ``` */ export function simpleActionGenOf( ...simpleActionGenFactories: Array< SimpleActionGenOrFactory | WeightedValue> > ): SimpleActionGenFactory { // Normalize weights. const weightedFactories = normalizeWeightedValues(simpleActionGenFactories) return (obj: ObjectType) => new Arbitrary>((rand: Random) => { // Loop until an action is generated based on weight. while (true) { // Select a generator/factory index. const dice = rand.inRange(0, weightedFactories.length) // Check weight probability. if (rand.nextBoolean(weightedFactories[dice].weight)) { const genOrFactory = weightedFactories[dice].value // Generate action: directly if generator, via factory otherwise. if (isSimpleActionGen(genOrFactory)) { return genOrFactory.generate(rand) } else { return genOrFactory(obj).generate(rand) } } } }) } /** * Creates an ActionGenFactory combining multiple weighted ActionGen or ActionGenFactory instances. * * @template ObjectType The type of the object. * @template ModelType The type of the model. * @param actionGenFactories Array of ActionGen, ActionGenFactory, or weighted versions. * @returns An ActionGenFactory selecting based on weights. * * @example * ```ts * const choose = Gen.actionOf( * Gen.just(new Action((_o: object, _m: object) => {}, 'a')), * Gen.just(new Action((_o: object, _m: object) => {}, 'b')) * ) * ``` */ export function actionGenOf( ...actionGenFactories: Array< ActionGenOrFactory | WeightedValue> > ): ActionGenFactory { // Normalize weights. const weightedFactories = normalizeWeightedValues(actionGenFactories) return (obj: ObjectType, model: ModelType) => new Arbitrary>((rand: Random) => { // Loop until an action is generated based on weight. while (true) { // Select a generator/factory index. const dice = rand.inRange(0, weightedFactories.length) // Check weight probability. if (rand.nextBoolean(weightedFactories[dice].weight)) { const genOrFactory = weightedFactories[dice].value // Generate action: directly if generator, via factory otherwise. if (isActionGen(genOrFactory)) { return genOrFactory.generate(rand) } else { return genOrFactory(obj, model).generate(rand) } } } }) }