Progress on action choices
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import Task from '/imports/api/engine/action/tasks/Task';
|
||||
|
||||
type InputProvider = {
|
||||
/**
|
||||
* Show the user the next property or task to apply and wait for input to continue
|
||||
*/
|
||||
nextStep?(task: Task): Promise<void>;
|
||||
/**
|
||||
* Roll dice
|
||||
* @param dice How many dice
|
||||
* @param diceSize How many faces per die
|
||||
*/
|
||||
rollDice(
|
||||
dice: { number: number, diceSize: number }[]
|
||||
): Promise<number[][]>;
|
||||
/**
|
||||
* Choose from a provided selection
|
||||
* @param action
|
||||
* @param choices Options to choose from
|
||||
* @param quantity Number of choices to make [min, max] inclusive, where -1 means no limit
|
||||
*/
|
||||
choose(
|
||||
choices: ({ _id: string } & Record<string, any>)[],
|
||||
quantity?: [min: number, max: number],
|
||||
): Promise<string[]>;
|
||||
/**
|
||||
* Get advantage, natural, or disadvantage for a d20 roll
|
||||
*/
|
||||
advantage(suggestedAdvantage: Advantage): Promise<Advantage>;
|
||||
/**
|
||||
* Get the details of a check or save
|
||||
*/
|
||||
//check(suggestedParams: CheckParams): Promise<CheckParams>;
|
||||
}
|
||||
|
||||
export type Advantage = 0 | 1 | -1;
|
||||
|
||||
export type CheckParams = {
|
||||
advantage: Advantage;
|
||||
skillVariableName: string;
|
||||
abilityVariableName: string;
|
||||
dc: number | null;
|
||||
contest?: true;
|
||||
targetSkillVariableName?: string;
|
||||
targetAbilityVariableName?: string;
|
||||
}
|
||||
|
||||
export default InputProvider;
|
||||
@@ -0,0 +1,28 @@
|
||||
import Alea from 'alea';
|
||||
|
||||
/**
|
||||
* Return a function that can be be used as InputProvider.rollDice
|
||||
* this function instance must be used for the entire action
|
||||
*/
|
||||
export default function getDeterministicDiceRoller(
|
||||
actionId: string
|
||||
): (dice: { number: number, diceSize: number }[]) => Promise<number[][]> {
|
||||
// Create a random number generator seeded on the ID of the action
|
||||
if (!actionId) throw new Meteor.Error('Id Required', 'action ID can not be ' + actionId)
|
||||
const randFrac = Alea(actionId);
|
||||
return (dice) => {
|
||||
const results: number[][] = [];
|
||||
for (const diceRoll of dice) {
|
||||
const values: number[] = [];
|
||||
if (diceRoll.number > 100) {
|
||||
throw new Meteor.Error('Too many dice', 'can only roll up to 100 dice at once');
|
||||
}
|
||||
for (let i = 0; i < diceRoll.number; i++) {
|
||||
const rolledValue = ~~(randFrac() * diceRoll.diceSize) + 1
|
||||
values.push(rolledValue);
|
||||
}
|
||||
results.push(values);
|
||||
}
|
||||
return Promise.resolve(results);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
import getDeterministicDiceRoller from '/imports/api/engine/action/functions/userInput/getDeterministicDiceRoller';
|
||||
|
||||
// This assumes the user's choices are in exactly the order they will be requested
|
||||
// Dice rolls are done fresh, no cheating
|
||||
export default function getReplayChoicesInputProvider(actionId: string, decisions: any[]):
|
||||
InputProvider {
|
||||
const dRoller = getDeterministicDiceRoller(actionId);
|
||||
const replaySavedInput: InputProvider = {
|
||||
nextStep() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
// To roll dice, ignore the user and use the deterministic dice roller again
|
||||
rollDice(dice) {
|
||||
decisions.pop();
|
||||
return dRoller(dice);
|
||||
},
|
||||
choose() {
|
||||
return Promise.resolve(decisions.pop());
|
||||
},
|
||||
advantage() {
|
||||
return Promise.resolve(decisions.pop());
|
||||
}
|
||||
}
|
||||
return replaySavedInput;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
|
||||
const inputProviderForTests: 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(dice = []) {
|
||||
const result: number[][] = [];
|
||||
for (const diceRoll of dice) {
|
||||
const averageRoll = Math.round(diceRoll.diceSize / 2);
|
||||
// Return an array full of averagely rolled dice, increasing by 1 for every dice
|
||||
result.push(
|
||||
new Array(diceRoll.number)
|
||||
.fill(averageRoll)
|
||||
.map((value, index) => (value + index - 1) % diceRoll.diceSize + 1)
|
||||
)
|
||||
}
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* For testing, always return the minimum number of choices, always choosing the first options
|
||||
*/
|
||||
async choose(choices, quantity = [1, 1]) {
|
||||
const chosen: string[] = [];
|
||||
const choiceQuantity = quantity[0] <= 0 ? 1 : quantity[0];
|
||||
for (let i = 0; i < choiceQuantity && i < choices.length; i += 1) {
|
||||
chosen.push(choices[i]._id);
|
||||
}
|
||||
return chosen;
|
||||
},
|
||||
/**
|
||||
* For testing, always return the suggested advantage, as if the user never chose differently
|
||||
*/
|
||||
async advantage(suggestedAdvantage) {
|
||||
return suggestedAdvantage;
|
||||
}
|
||||
}
|
||||
|
||||
export default inputProviderForTests;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
|
||||
/**
|
||||
* Create a new version of the user input function, that saves the user's choices to an array
|
||||
* before returning them
|
||||
*/
|
||||
export default function saveInputChoices(action: EngineAction, userInput: InputProvider): InputProvider {
|
||||
const newInputProvider: Partial<InputProvider> = {};
|
||||
|
||||
if (!action._choices) {
|
||||
action._choices = [];
|
||||
}
|
||||
|
||||
// For every function in the given input provider
|
||||
for (const key in userInput) {
|
||||
const oldFn = userInput[key];
|
||||
// Make a new function that does the same thing, but saves the result to action._choices
|
||||
const newFn = (...args) => {
|
||||
const result = oldFn(...args);
|
||||
action._choices.push(result);
|
||||
return result;
|
||||
}
|
||||
newInputProvider[key] = newFn;
|
||||
}
|
||||
|
||||
return newInputProvider as InputProvider;
|
||||
}
|
||||
Reference in New Issue
Block a user