Fixed failing action engine tests, moved more engine parts to ts

This commit is contained in:
Thaum Rystra
2024-02-18 23:39:16 +02:00
parent c721374278
commit 55a6b16c31
15 changed files with 177 additions and 100 deletions

View File

@@ -0,0 +1,9 @@
import { EngineAction } from '/imports/api/engine/action/EngineActions';
type InputProvider = {
rollDice(
action: EngineAction, dice: { number: number, diceSize: number }[]
): Promise<number[][]>;
}
export default InputProvider;

View File

@@ -1,4 +1,3 @@
import { assert } from 'chai';
import '/imports/api/simpleSchemaConfig.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
import { propsFromForest } from '/imports/api/properties/tests/propTestBuilder.testFn';
@@ -9,6 +8,7 @@ import { loadCreature } from '/imports/api/engine/loadCreatures';
import EngineActions, { EngineAction } from '/imports/api/engine/action/EngineActions';
import { applyAction } from '/imports/api/engine/action/functions/applyAction';
import { LogContent, Mutation, Removal, Update } from '/imports/api/engine/action/tasks/TaskResult';
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
/**
* Removes all creatures, properties, and creatureVariable documents from the database
@@ -59,12 +59,12 @@ export const randomIds = new Array(100).fill(undefined).map(() => Random.id());
* @param userInputFn A function that simulates user input
* @returns The Engine Action with mutations resulting from running the action
*/
export async function runActionById(propId, targetIds?, userInputFn = () => 0) {
export async function runActionById(propId, targetIds?, userInput = testInputProvider) {
const prop = await CreatureProperties.findOneAsync(propId);
const actionId = await createAction(prop, targetIds);
const action = await EngineActions.findOneAsync(actionId);
if (!action) throw 'Action is expected to exist';
await applyAction(action, userInputFn, { simulate: true });
await applyAction(action, userInput, { simulate: true });
return action;
}
@@ -148,3 +148,25 @@ export function allLogContent(action: EngineAction) {
});
return contents;
}
const testInputProvider: InputProvider = {
/**
* For testing, randomness is hard to deal with
* rollDice function returns the average roll for every dice rolled
* [5d10, 1d4] => [[6,6,6,6,6], [3]]
*/
async rollDice(action, dice) {
const result: number[][] = [];
for (const diceRoll of dice) {
const averageRoll = Math.round(diceRoll.diceSize / 2);
// Return an array full of averagely rolled dice
result.push(
new Array(diceRoll.number)
.fill(averageRoll)
)
}
return result;
}
}
export { testInputProvider }

View File

@@ -1,7 +1,8 @@
import { EngineAction, ActionSchema } from '/imports/api/engine/action/EngineActions';
import EngineActions, { EngineAction, ActionSchema } from '/imports/api/engine/action/EngineActions';
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
import applyTask from '/imports/api/engine/action/tasks/applyTask'
import { isEmpty } from 'lodash';
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
// TODO create a function to get the effective value of a property,
// simulating all the result updates in the action so far
@@ -10,12 +11,12 @@ import { isEmpty } from 'lodash';
// This is run once as a simulation on the client awaiting all the various inputs or step through
// clicks from the user, then it is run as part of the runAction method, where it is expected to
// complete instantly on the client, and sent to the server as a method call
export async function applyAction(action: EngineAction, userInput?: any[] | Function, options?: {
export async function applyAction(action: EngineAction, userInput: InputProvider, options?: {
simulate?: boolean, stepThrough?: boolean
}) {
const { simulate, stepThrough } = options || {};
if (!simulate && stepThrough) throw 'Cannot step through unless simulating';
if (simulate && typeof userInput !== 'function') throw 'Must provide a function to get user input when simulating';
if (simulate && !userInput) throw 'Must provide a function to get user input when simulating';
action._stepThrough = stepThrough;
action._isSimulation = simulate;
@@ -37,6 +38,6 @@ function writeChangedAction(original: EngineAction, changed: EngineAction) {
}
}
if (!isEmpty($set) && original._id) {
return Actions.updateAsync(original._id, { $set });
return EngineActions.updateAsync(original._id, { $set });
}
}

View File

@@ -2,25 +2,33 @@ import { Context, toPrimitiveOrString } from '/imports/parser/resolve';
import {
aggregateCalculationEffects,
aggregateCalculationProficiencies,
resolveCalculationNode,
} from '/imports/api/engine/computation/computeComputation/computeByType/computeCalculation';
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
import resolve from '/imports/parser/resolve';
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
import { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
import { EngineAction } from '/imports/api/engine/action/EngineActions';
// TODO Redo the work of
// imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js
// But in the action scope
export default async function recalculateCalculation(
calcObj, action, parseLevel = 'reduce', context, scope
calcObj: CalculatedField,
action,
parseLevel: ResolveLevel = 'reduce',
userInput: InputProvider,
) {
if (!calcObj?.parseNode) return;
calcObj._parseLevel = parseLevel;
if (!scope) {
scope = await getEffectiveActionScope(action);
}
// Re-resolve the parse node
resolveCalculationNode(calcObj, calcObj.parseNode, scope, context);
const scope = await getEffectiveActionScope(action);
// Re-resolve the parse node before effects and proficiencies
const {
result: unaffectedResult,
context
} = resolve(parseLevel, calcObj.parseNode, scope);
calcObj.valueNode = unaffectedResult;
// store the unaffected value
if (calcObj.effectIds || calcObj.proficiencyIds) {
calcObj.unaffected = toPrimitiveOrString(calcObj.valueNode);
@@ -35,19 +43,28 @@ export default async function recalculateCalculation(
id => getSingleProperty(action.creatureId, id),
scope['proficiencyBonus']?.value || 0
);
// Resolve the modified valueNode
resolveCalculationNode(calcObj, calcObj.valueNode, scope, context);
// Store the primitive value
calcObj.value = toPrimitiveOrString(calcObj.valueNode);
// TODO log errors
// Resolve the modified valueNode, use the same context
const {
result: finalResult
} = resolve(parseLevel, calcObj.parseNode, scope, context);
// Store the errors
calcObj.errors = context.errors;
// Store the value and its primitive
calcObj.value = toPrimitiveOrString(finalResult);
calcObj.valueNode = finalResult;
}
export async function rollAndReduceCalculation(calcObj, action) {
export async function rollAndReduceCalculation(
calcObj: CalculatedField, action: EngineAction, userInput: InputProvider
) {
const context = new Context();
const scope = await getEffectiveActionScope(action);
// Compile
recalculateCalculation(calcObj, action, 'compile', context, scope);
recalculateCalculation(calcObj, action, 'compile', userInput);
const compiled = calcObj.valueNode;
// Roll