- /**
- * @module exp-player
- * @submodule randomizers
- */
-
- /**
- * Randomizer to implement flexible condition assignment and counterbalancing by
- * allowing the user to specify an arbitrary sequence of frames to create. A
- * set of parameters is randomly selected from a list of available parameterSets,
- * and these parameters are substituted in to the parameters specified in the
- * list of frames.
- *
- * To use, define a frame with "kind": "choice" and "sampler": "random-parameter-set",
- * as shown below, in addition to the parameters described under 'properties'.
- *
- * This
- *
- ```json
- "frames": {
- "test-trials": {
- "sampler": "random-parameter-set",
- "kind": "choice",
- "id": "test-trials",
- "commonFrameProperties": {
- "kind": "exp-lookit-story-page",
- "baseDir": "https://s3.amazonaws.com/lookitcontents/ingroupobligations/",
- "audioTypes": ["mp3", "ogg"],
- "doRecording": true,
- "autoProceed": false,
- "parentTextBlock": {
- "title": "Parents!",
- "text": "Common instructions across test trials here",
- "emph": true
- }
- },
- "frameList": [
- {
- "images": [
- {
- "id": "agent",
- "src": "AGENTIMG1",
- "left": "40",
- "width": "20",
- "top": "10"
- },
- {
- "id": "left",
- "src": "LEFTIMG1",
- "left": "10",
- "width": "20",
- "top": "50"
- },
- {
- "id": "right",
- "src": "RIGHTIMG1",
- "left": "70",
- "width": "20",
- "top": "50"
- }
- ],
- "audioSources": [
- {
- "audioId": "questionaudio",
- "sources": [{"stub": "QUESTION1AUDIO"}],
- "highlights": "QUESTION1HIGHLIGHTS"
- }
- ]
- },
- {
- "images": [
- {
- "id": "agent",
- "src": "AGENTIMG2",
- "left": "40",
- "width": "20",
- "top": "10"
- },
- {
- "id": "left",
- "src": "LEFTIMG2",
- "left": "10",
- "width": "20",
- "top": "50"
- },
- {
- "id": "right",
- "src": "RIGHTIMG2",
- "left": "70",
- "width": "20",
- "top": "50"
- }
- ],
- "audioSources": [
- {
- "audioId": "questionaudio",
- "sources": [{"stub": "QUESTION2AUDIO"}],
- "highlights": "QUESTION2HIGHLIGHTS"
- }
- ]
- }
- ],
- "parameterSets": [
- {
- "AGENTIMG1": "flurpagent1.jpg",
- "LEFTIMG1": "flurpvictim1.jpg",
- "RIGHTIMG1": "zazzvictim1.jpg",
- "QUESTION1AUDIO": "flurpleftmean1",
- "QUESTION1HIGHLIGHTS": [
- {"range": [0.399293, 3.617124], "image": "agent"},
- {"range": [5.085112, 6.811467], "image": "left"},
- {"range": [6.905418, 8.702236], "image": "right"}
- ],
- "AGENTIMG2": "flurpagent2.jpg",
- "LEFTIMG2": "flurpvictim2.jpg",
- "RIGHTIMG2": "zazzvictim2.jpg",
- "QUESTION2AUDIO": "flurpleftinduct1",
- "QUESTION2HIGHLIGHTS": [
- {"range": [0.372569, 5.309110], "image": "agent"},
- {"range": [5.495395, 7.209213], "image": "left"},
- {"range": [5.495395, 7.209213], "image": "right"},
- {"range": [9.966225, 11.922212], "image": "left"},
- {"range": [12.052612, 14.008600], "image": "right"}
- ]
- },
- {
- "AGENTIMG1": "zazzagent1.jpg",
- "LEFTIMG1": "flurpvictim1.jpg",
- "RIGHTIMG1": "zazzvictim1.jpg",
- "QUESTION1AUDIO": "zazzrightnice1",
- "QUESTION1HIGHLIGHTS": [
- {"range": [0.348454, 3.736871], "image": "agent"},
- {"range": [5.395033, 6.884975], "image": "left"},
- {"range": [6.969085, 8.975701], "image": "right"}
- ],
- "AGENTIMG2": "zazzagent2.jpg",
- "LEFTIMG2": "flurpvictim2.jpg",
- "RIGHTIMG2": "zazzvictim2.jpg",
- "QUESTION2AUDIO": "zazzrightinduct1",
- "QUESTION2HIGHLIGHTS": [
- {"range": [0.572920, 5.138376], "image": "agent"},
- {"range": [5.335317, 7.089884], "image": "left"},
- {"range": [5.335317, 7.089884], "image": "right"},
- {"range": [9.721735, 11.565821], "image": "left"},
- {"range": [11.655340, 13.535233], "image": "right"}
- ]
- }
- ],
- "parameterSetWeights": [1, 1]
- }
- }
-
- * ```
- * @class randomParameterSet
- */
-
- function getRandomElement(arr, weights) {
- var totalProb = weights.reduce((a, b) => a + b, 0);
- var randPos = Math.random() * totalProb;
-
- var weightSum = 0;
- for (var i = 0; i < arr.length; i++) {
- weightSum += weights[i];
- if (randPos <= weightSum) {
- return [i, arr[i]];
- }
- }
- }
-
- function replaceValues(obj, rep) {
- for (var property in obj) {
- if (obj.hasOwnProperty(property)) {
- if (typeof obj[property] === 'object') {
- obj[property] = replaceValues(obj[property], rep);
- } else {
- if (rep.hasOwnProperty(obj[property])) {
- obj[property] = rep[obj[property]];
- }
- }
- }
- }
- return obj;
- }
-
- var randomizer = function(frameId, frameConfig, pastSessions, resolveFrame) {
-
- // Data provided to randomizer (properties of frameConfig):
-
- /**
- * Object describing common parameters to use in EVERY frame created
- * by this randomizer. Parameter names and values are as described in
- * the documentation for the frameType used.
- *
- * @property {Object} commonFrameProperties
- */
-
- /**
- * Unique string identifying this set of frames
- *
- * @property {String} id
- */
-
- /**
- * List of frames to be created by this randomizer. Each frame is an
- * object with any necessary frame-specific properties specified. The
- * 'kind' of frame can be specified either here (per frame) or in
- * commonFrameProperties. If a property is defined for a given frame both
- * in this frame list and in commonFrameProperties, the value in the frame
- * list will take precedence.
- *
- * (E.g., you could include 'kind': 'normal-frame' in
- * commmonFrameProperties, but for a single frame in frameList, include
- * 'kind': 'special-frame'.)
- *
- * Any property VALUES within any of the frames in this list which match
- * a property NAME in the selected parameterSet will be replaced by the
- * corresponding parameterSet value. E.g., suppose a frame in frameList is
- *
- * ```
- * {'leftImage': 'LEFTIMAGE1',
- * 'rightImage': 'frog.jpg',
- * 'size': 'IMAGESIZE'}
- * ```
- *
- * and the row that has been selected randomly of parameterSets is
- *
- * ```
- * {'LEFTIMAGE1': 'toad.jpg',
- 'LEFTIMAGE2': 'dog.jpg',
- 'IMAGESIZE': 250}
- * ```
- *
- * Then the frame would be transformed into:
- * ```
- * {'leftImage': 'toad.jpg',
- * 'rightImage': 'frog.jpg',
- * 'size': 250}
- * ```
- *
- * The same values may be applied across multiple frames. For instance,
- * suppose frameList is
-
- ```
- [
- {
- 'leftImage': 'LEFTIMAGE1',
- 'rightImage': 'frog.jpg',
- 'size': 'IMAGESIZE'
- },
- {
- 'leftImage': 'LEFTIMAGE2',
- 'rightImage': 'frog.jpg',
- 'size': 'IMAGESIZE'
- }
- ]
- ```
-
- * Then the corresponding processed frames would include the values
- ```
- [
- {
- 'leftImage': 'toad.jpg',
- 'rightImage': 'frog.jpg',
- 'size': 250
- },
- {
- 'leftImage': 'dog.jpg',
- 'rightImage': 'frog.jpg',
- 'size': 250
- }
- ]
- ```
- * A property value like 'IMAGESIZE' may be placed in a frame definition
- * nested within another object (at any depth) or within a list and
- * will still be replaced.
-
- * @property {Object[]} frameList
- */
-
- /**
- * Array of parameter sets to randomly select from in order to determine
- * the parameters for each frame in this session.
- *
- * A single element of parameterSets will be applied to a given session.
- *
- * @property {Object[]} parameterSets
- */
-
- /**
- * [Optional] Array of weights for parameter sets; elements correspond to
- * elements of parameterSets. The probability of selecting an element
- * parameterSets[i] is parameterSetWeights[i]/sum(parameterSetWeights).
- *
- * If not provided, all parameterSets are weighted equally.
- *
- * This is intended to allow manual control of counterbalancing during
- * data collection, e.g. to allow one condition to "catch up" if it was
- * randomly selected less often.
- *
- * @property {Number[]} parameterSetWeights
- */
-
- // Select a parameter set to use for this trial.
- if (!(frameConfig.hasOwnProperty('parameterSetWeights'))) {
- frameConfig.parameterSetWeights = new Array(frameConfig.parameterSets.length).fill(1);
- }
-
- var parameterData = getRandomElement(frameConfig.parameterSets, frameConfig.parameterSetWeights);
- var parameterSetIndex = parameterData[0];
- var parameterSet = parameterData[1];
-
- var frames = [];
- var thisFrame = {};
-
- for (var iFrame = 0; iFrame < frameConfig.frameList.length; iFrame++) {
-
- // Assign parameters common to all frames made by this randomizer
- thisFrame = {};
- Object.assign(thisFrame, frameConfig.commonFrameProperties);
-
- // Assign parameters specific to this frame (allow to override
- // common parameters assigned above)
- Object.assign(thisFrame, frameConfig.frameList[iFrame]);
-
- // Substitute any properties that can be replaced based on
- // the parameter set.
- thisFrame = replaceValues(thisFrame, parameterSet);
-
- // Assign frame ID
- thisFrame.id = `${frameId}`;
-
- thisFrame = resolveFrame(thisFrame.id, thisFrame)[0];
- frames.push(...thisFrame); // spread syntax important here -- a list of frames is returned by resolveFrame.
- }
-
- return [frames, {'conditionNum': parameterSetIndex, 'parameterSet': parameterSet}];
-
- };
- export default randomizer;
-
- // Export helper functions to support unit testing
- export { getRandomElement, replaceValues};
-
-